diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..31a9930
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,5 @@
+[*.{html,css,js,ts,jsx,tsx,json}]
+charset = utf-8
+indent_style = tab
+indent_size = 2
+quote_type= single
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..4ca0343
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+VITE_GRAPHQL_SUPPORT_API=https://example.com/graphql
+VITE_PAYMENT_API=https://example.com/payment/api
+VITE_GRAPHQL_SUPPORT_SUBSCRIPTIONS_API=https://example.com/graphql
+VITE_GRAPHQL_API=https://example.com/graphql
+VITE_STRIPE_PUBLIC_KEY=STRIPE_PUBLIC_KEY
+VITE_CLOUDINARY_URL=CLOUDINARY_URL
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000..82a9a73
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1 @@
+.eslintrc.js
\ No newline at end of file
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..ac9efb0
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,65 @@
+module.exports = {
+ extends: [
+ 'airbnb-typescript',
+ 'airbnb/hooks',
+ 'plugin:jest/recommended',
+ 'plugin:prettier/recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:import/recommended'
+ ],
+ plugins: ['react', '@typescript-eslint', 'jest', 'prettier'],
+ env: {
+ browser: true,
+ es6: true,
+ jest: true,
+ },
+ globals: {
+ Atomics: 'readonly',
+ SharedArrayBuffer: 'readonly',
+ },
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ },
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ project: './tsconfig.json',
+ },
+ rules: {
+ 'no-console': 0,
+ 'no-nested-ternary': 'off',
+ 'no-unused-expressions': 'off',
+ '@typescript-eslint/no-unused-expressions': [
+ 'warn',
+ { allowShortCircuit: true, allowTernary: true },
+ ],
+ '@typescript-eslint/ban-ts-comment': 0,
+ 'react-hooks/exhaustive-deps': 1,
+ 'import/no-cycle': 'off',
+ 'react/jsx-props-no-spreading': 'off',
+ 'react/require-default-props': 0,
+ 'import/prefer-default-export': 'off',
+ 'import/no-extraneous-dependencies': 0,
+ 'no-prototype-builtins': 'off',
+ 'no-plusplus': 'off',
+ '@typescript-eslint/no-use-before-define': 'off',
+ '@typescript-eslint/no-non-null-asserted-optional-chain': 0,
+ '@typescript-eslint/no-non-null-assertion': 0,
+ 'react/prop-types': 'off',
+ 'react/react-in-jsx-scope': 'off',
+ 'react/self-closing-comp': 0,
+ 'react/no-array-index-key': 0,
+ '@typescript-eslint/camelcase': 'off',
+ '@typescript-eslint/explicit-function-return-type': 0,
+ '@typescript-eslint/explicit-module-boundary-types': 0,
+ '@typescript-eslint/no-explicit-any': 0,
+ 'linebreak-style': 'off',
+ 'prettier/prettier': [
+ 'error',
+ {
+ endOfLine: 'auto',
+ },
+ ],
+ },
+};
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c5b47ee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,28 @@
+# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
+
+# dependencies
+/node_modules
+/.pnp
+.pnp.js
+
+# development
+/codegen-support.ts
+/codegen-main.yml
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..16ad5a2
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,9 @@
+{
+ "singleQuote": true,
+ "jsxSingleQuote": true,
+ "tabWidth": 2,
+ "semi": true,
+ "arrowParens": "always",
+ "useTabs": false,
+ "endOfLine": "lf"
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..46a45a0
--- /dev/null
+++ b/index.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Astrobuild
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..fa7f242
--- /dev/null
+++ b/package.json
@@ -0,0 +1,77 @@
+{
+ "name": "astrobuild",
+ "version": "0.1.0",
+ "private": true,
+ "dependencies": {
+ "@apollo/client": "^3.7.10",
+ "@testing-library/jest-dom": "^5.16.5",
+ "@testing-library/react": "^14.0.0",
+ "@testing-library/user-event": "^14.4.3",
+ "@types/jest": "^29.5.0",
+ "@types/jwt-decode": "^3.1.0",
+ "@types/node": "^18.15.7",
+ "@types/react": "^18.0.29",
+ "@types/react-dom": "^18.0.11",
+ "@types/react-router-dom": "^5.3.3",
+ "@types/styled-components": "^5.1.26",
+ "formik": "^2.2.9",
+ "graphql": "^16.6.0",
+ "graphql-ws": "^5.13.1",
+ "jwt-decode": "^3.1.2",
+ "localforage": "^1.10.0",
+ "match-sorter": "^6.3.1",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-elastic-carousel": "^0.11.5",
+ "react-router-dom": "^6.9.0",
+ "react-to-print": "^2.14.12",
+ "reactflow": "^11.7.0",
+ "sort-by": "^1.2.0",
+ "styled-components": "^5.3.10",
+ "typescript": "^5.0.2",
+ "vite-plugin-svgr": "^2.4.0",
+ "web-vitals": "^3.3.0",
+ "yup": "^1.0.2"
+ },
+ "scripts": {
+ "start": "vite",
+ "build": "vite build",
+ "generate:main": "graphql-codegen --config codegen-main.yml",
+ "lint": "yarn run eslint src --ext .ts,.tsx",
+ "fix": "yarn lint --fix",
+ "generate:support": "graphql-codegen --config codegen-support.ts"
+ },
+ "browserslist": {
+ "production": [
+ ">0.2%",
+ "not dead",
+ "not op_mini all"
+ ],
+ "development": [
+ "last 1 chrome version",
+ "last 1 firefox version",
+ "last 1 safari version"
+ ]
+ },
+ "devDependencies": {
+ "@graphql-codegen/cli": "3.3.1",
+ "@graphql-codegen/client-preset": "3.0.1",
+ "@graphql-codegen/introspection": "3.0.1",
+ "@graphql-codegen/typescript": "^3.0.2",
+ "@graphql-codegen/typescript-operations": "^3.0.2",
+ "@typescript-eslint/eslint-plugin": "^5.56.0",
+ "@typescript-eslint/parser": "^5.56.0",
+ "@vitejs/plugin-react": "^4.0.0",
+ "eslint-config-airbnb": "19.0.4",
+ "eslint-config-airbnb-typescript": "^17.0.0",
+ "eslint-config-prettier": "^8.8.0",
+ "eslint-plugin-import": "2.27.5",
+ "eslint-plugin-jest": "^27.2.1",
+ "eslint-plugin-jsx-a11y": "6.7.1",
+ "eslint-plugin-prettier": "^4.2.1",
+ "eslint-plugin-react": "7.32.2",
+ "eslint-plugin-react-hooks": "4.6.0",
+ "prettier": "^2.8.7",
+ "vite": "^4.2.1"
+ }
+}
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 0000000..b069b88
Binary files /dev/null and b/public/favicon.ico differ
diff --git a/public/logo192.png b/public/logo192.png
new file mode 100644
index 0000000..fc44b0a
Binary files /dev/null and b/public/logo192.png differ
diff --git a/public/logo512.png b/public/logo512.png
new file mode 100644
index 0000000..a4e47a6
Binary files /dev/null and b/public/logo512.png differ
diff --git a/public/manifest.json b/public/manifest.json
new file mode 100644
index 0000000..080d6c7
--- /dev/null
+++ b/public/manifest.json
@@ -0,0 +1,25 @@
+{
+ "short_name": "React App",
+ "name": "Create React App Sample",
+ "icons": [
+ {
+ "src": "favicon.ico",
+ "sizes": "64x64 32x32 24x24 16x16",
+ "type": "image/x-icon"
+ },
+ {
+ "src": "logo192.png",
+ "type": "image/png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "logo512.png",
+ "type": "image/png",
+ "sizes": "512x512"
+ }
+ ],
+ "start_url": ".",
+ "display": "standalone",
+ "theme_color": "#000000",
+ "background_color": "#ffffff"
+}
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 0000000..e9e57dc
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,3 @@
+# https://www.robotstxt.org/robotstxt.html
+User-agent: *
+Disallow:
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..0917687
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,352 @@
+import jwtDecode from 'jwt-decode';
+import { useEffect } from 'react';
+import { Routes, Route, Navigate } from 'react-router-dom';
+import { useLazyQuery, useReactiveVar } from '@apollo/client';
+import { Protected, Public, Navbar, Sidebar, Spinner } from './components';
+import { roleVar, tokenVar, userVar } from './graphql/state';
+import {
+ AdditionalInfo,
+ ForgotPassword,
+ Login,
+ RecoverAccount,
+ Signup,
+ Project,
+ Users,
+ Settings,
+ UserSettings,
+ CreateUser,
+ Template,
+ Feature,
+ Category,
+ Prototype,
+ AddCategory,
+ AddFeature,
+ AddTemplate,
+ CategorySettings,
+ FeatureSettings,
+ TemplateSettings,
+ AddProject,
+ UpdateProject,
+ Support,
+} from './pages';
+import { GetUserByIdQuery, GetUserByIdQueryVariables } from './graphql/types';
+import { GET_USER_BY_ID } from './graphql/auth.api';
+
+const App = () => {
+ const token = useReactiveVar(tokenVar);
+ const role = useReactiveVar(roleVar);
+ const currentUser = useReactiveVar(userVar);
+
+ const [getUserById, { loading }] = useLazyQuery<
+ GetUserByIdQuery,
+ GetUserByIdQueryVariables
+ >(GET_USER_BY_ID, {
+ onCompleted({ getUserById: user }) {
+ userVar(user);
+ switch (user.role) {
+ case 'Client':
+ roleVar('client');
+ break;
+ case 'ProductOwner':
+ roleVar('productOwner');
+ break;
+ case 'Developer':
+ roleVar('developer');
+ break;
+ case 'Admin':
+ roleVar('admin');
+ break;
+ default:
+ break;
+ }
+ },
+ });
+
+ useEffect(() => {
+ const localStorageToken = localStorage.getItem('token');
+
+ if (localStorageToken) {
+ const { id } = jwtDecode<{ id: string; role: string }>(localStorageToken);
+
+ getUserById({ variables: { id } });
+ tokenVar(localStorageToken);
+ }
+ }, []);
+
+ return !loading ? (
+ <>
+ {token && currentUser?.firstName && (
+ <>
+
+
+ >
+ )}
+
+
+ {role !== 'admin' ? (
+
+ ) : (
+
+ )}
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+ >
+ ) : (
+
+ );
+};
+
+export default App;
diff --git a/src/GlobalStyles.tsx b/src/GlobalStyles.tsx
new file mode 100644
index 0000000..5f09c4d
--- /dev/null
+++ b/src/GlobalStyles.tsx
@@ -0,0 +1,20 @@
+import { createGlobalStyle } from 'styled-components';
+
+const GlobalStyles = createGlobalStyle`
+ * {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ font-family: 'Inter', sans-serif;
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 1.5;
+ outline: none;
+ }
+
+ ul {
+ list-style: none
+ }
+`;
+
+export default GlobalStyles;
diff --git a/src/assets/icons/add.svg b/src/assets/icons/add.svg
new file mode 100644
index 0000000..bf4bfa9
--- /dev/null
+++ b/src/assets/icons/add.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/arrow-left.svg b/src/assets/icons/arrow-left.svg
new file mode 100644
index 0000000..36c5bf8
--- /dev/null
+++ b/src/assets/icons/arrow-left.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/arrow-right.svg b/src/assets/icons/arrow-right.svg
new file mode 100644
index 0000000..f03a188
--- /dev/null
+++ b/src/assets/icons/arrow-right.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/attachment.svg b/src/assets/icons/attachment.svg
new file mode 100644
index 0000000..6f5de98
--- /dev/null
+++ b/src/assets/icons/attachment.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/backend.svg b/src/assets/icons/backend.svg
new file mode 100644
index 0000000..a5571fc
--- /dev/null
+++ b/src/assets/icons/backend.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/check-circle.svg b/src/assets/icons/check-circle.svg
new file mode 100644
index 0000000..518a206
--- /dev/null
+++ b/src/assets/icons/check-circle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/check.svg b/src/assets/icons/check.svg
new file mode 100644
index 0000000..f6ec647
--- /dev/null
+++ b/src/assets/icons/check.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/chevron-down.svg b/src/assets/icons/chevron-down.svg
new file mode 100644
index 0000000..7a0badc
--- /dev/null
+++ b/src/assets/icons/chevron-down.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/chevron-left.svg b/src/assets/icons/chevron-left.svg
new file mode 100644
index 0000000..747d46d
--- /dev/null
+++ b/src/assets/icons/chevron-left.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/chevron-right.svg b/src/assets/icons/chevron-right.svg
new file mode 100644
index 0000000..258de41
--- /dev/null
+++ b/src/assets/icons/chevron-right.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/close.svg b/src/assets/icons/close.svg
new file mode 100644
index 0000000..7d5875c
--- /dev/null
+++ b/src/assets/icons/close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/delete.svg b/src/assets/icons/delete.svg
new file mode 100644
index 0000000..3eed3f5
--- /dev/null
+++ b/src/assets/icons/delete.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/design.svg b/src/assets/icons/design.svg
new file mode 100644
index 0000000..f49fe21
--- /dev/null
+++ b/src/assets/icons/design.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/edit.svg b/src/assets/icons/edit.svg
new file mode 100644
index 0000000..183ac1d
--- /dev/null
+++ b/src/assets/icons/edit.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/features.svg b/src/assets/icons/features.svg
new file mode 100644
index 0000000..076b1f0
--- /dev/null
+++ b/src/assets/icons/features.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/frontend.svg b/src/assets/icons/frontend.svg
new file mode 100644
index 0000000..77d4b1d
--- /dev/null
+++ b/src/assets/icons/frontend.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/full-build.svg b/src/assets/icons/full-build.svg
new file mode 100644
index 0000000..c4954b5
--- /dev/null
+++ b/src/assets/icons/full-build.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/general.svg b/src/assets/icons/general.svg
new file mode 100644
index 0000000..a9b7206
--- /dev/null
+++ b/src/assets/icons/general.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/google.svg b/src/assets/icons/google.svg
new file mode 100644
index 0000000..7e2b775
--- /dev/null
+++ b/src/assets/icons/google.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/logo.svg b/src/assets/icons/logo.svg
new file mode 100644
index 0000000..54ce7a8
--- /dev/null
+++ b/src/assets/icons/logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/src/assets/icons/logout.svg b/src/assets/icons/logout.svg
new file mode 100644
index 0000000..5d53469
--- /dev/null
+++ b/src/assets/icons/logout.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/assets/icons/messaging.svg b/src/assets/icons/messaging.svg
new file mode 100644
index 0000000..b4f4483
--- /dev/null
+++ b/src/assets/icons/messaging.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/mvp.svg b/src/assets/icons/mvp.svg
new file mode 100644
index 0000000..8fdafa9
--- /dev/null
+++ b/src/assets/icons/mvp.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/payment.svg b/src/assets/icons/payment.svg
new file mode 100644
index 0000000..17a8e63
--- /dev/null
+++ b/src/assets/icons/payment.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/profile.svg b/src/assets/icons/profile.svg
new file mode 100644
index 0000000..ee876e4
--- /dev/null
+++ b/src/assets/icons/profile.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/search.svg b/src/assets/icons/search.svg
new file mode 100644
index 0000000..dfd79bd
--- /dev/null
+++ b/src/assets/icons/search.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/security.svg b/src/assets/icons/security.svg
new file mode 100644
index 0000000..540d9eb
--- /dev/null
+++ b/src/assets/icons/security.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/send.svg b/src/assets/icons/send.svg
new file mode 100644
index 0000000..ed725ea
--- /dev/null
+++ b/src/assets/icons/send.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/settings.svg b/src/assets/icons/settings.svg
new file mode 100644
index 0000000..b6ef6c6
--- /dev/null
+++ b/src/assets/icons/settings.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/specification.svg b/src/assets/icons/specification.svg
new file mode 100644
index 0000000..988bf0f
--- /dev/null
+++ b/src/assets/icons/specification.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/icons/upload.svg b/src/assets/icons/upload.svg
new file mode 100644
index 0000000..fcf9bce
--- /dev/null
+++ b/src/assets/icons/upload.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/assets/images/empty.svg b/src/assets/images/empty.svg
new file mode 100644
index 0000000..250db80
--- /dev/null
+++ b/src/assets/images/empty.svg
@@ -0,0 +1,35 @@
+
diff --git a/src/assets/images/login.svg b/src/assets/images/login.svg
new file mode 100644
index 0000000..e7db1bb
--- /dev/null
+++ b/src/assets/images/login.svg
@@ -0,0 +1,94 @@
+
diff --git a/src/assets/images/signup.svg b/src/assets/images/signup.svg
new file mode 100644
index 0000000..faaa29b
--- /dev/null
+++ b/src/assets/images/signup.svg
@@ -0,0 +1,58 @@
+
diff --git a/src/assets/images/thread-client.svg b/src/assets/images/thread-client.svg
new file mode 100644
index 0000000..8dc82bb
--- /dev/null
+++ b/src/assets/images/thread-client.svg
@@ -0,0 +1,125 @@
+
diff --git a/src/assets/images/thread-po.svg b/src/assets/images/thread-po.svg
new file mode 100644
index 0000000..e6f9afe
--- /dev/null
+++ b/src/assets/images/thread-po.svg
@@ -0,0 +1,125 @@
+
diff --git a/src/assets/index.ts b/src/assets/index.ts
new file mode 100644
index 0000000..5cfa15f
--- /dev/null
+++ b/src/assets/index.ts
@@ -0,0 +1,75 @@
+import { ReactComponent as Add } from './icons/add.svg';
+import { ReactComponent as Upload } from './icons/upload.svg';
+import { ReactComponent as ChevronDown } from './icons/chevron-down.svg';
+import { ReactComponent as ChevronLeft } from './icons/chevron-left.svg';
+import { ReactComponent as ChevronRight } from './icons/chevron-right.svg';
+import { ReactComponent as ArrowLeft } from './icons/arrow-left.svg';
+import { ReactComponent as ArrowRight } from './icons/arrow-right.svg';
+import { ReactComponent as Search } from './icons/search.svg';
+import { ReactComponent as Check } from './icons/check.svg';
+import { ReactComponent as CheckCircle } from './icons/check-circle.svg';
+import { ReactComponent as Google } from './icons/google.svg';
+import { ReactComponent as Settings } from './icons/settings.svg';
+import { ReactComponent as Logout } from './icons/logout.svg';
+import { ReactComponent as Logo } from './icons/logo.svg';
+import { ReactComponent as Profile } from './icons/profile.svg';
+import { ReactComponent as Security } from './icons/security.svg';
+import { ReactComponent as Edit } from './icons/edit.svg';
+import { ReactComponent as Delete } from './icons/delete.svg';
+import { ReactComponent as General } from './icons/general.svg';
+import { ReactComponent as Design } from './icons/design.svg';
+import { ReactComponent as FullBuild } from './icons/full-build.svg';
+import { ReactComponent as MVP } from './icons/mvp.svg';
+import { ReactComponent as Specification } from './icons/specification.svg';
+import { ReactComponent as Features } from './icons/features.svg';
+import { ReactComponent as Frontend } from './icons/frontend.svg';
+import { ReactComponent as Backend } from './icons/backend.svg';
+import { ReactComponent as Close } from './icons/close.svg';
+import { ReactComponent as Payment } from './icons/payment.svg';
+import { ReactComponent as Messaging } from './icons/messaging.svg';
+import { ReactComponent as Send } from './icons/send.svg';
+import { ReactComponent as Attachment } from './icons/attachment.svg';
+import { ReactComponent as Login } from './images/login.svg';
+import { ReactComponent as Signup } from './images/signup.svg';
+import { ReactComponent as Empty } from './images/empty.svg';
+import { ReactComponent as ThreadClient } from './images/thread-client.svg';
+import { ReactComponent as ThreadProductOwner } from './images/thread-po.svg';
+
+export {
+ Add,
+ Upload,
+ ChevronDown,
+ ChevronLeft,
+ ChevronRight,
+ ArrowLeft,
+ ArrowRight,
+ Search,
+ Check,
+ CheckCircle,
+ Google,
+ Settings,
+ Logout,
+ Logo,
+ Profile,
+ Security,
+ Edit,
+ Delete,
+ General,
+ Design,
+ FullBuild,
+ MVP,
+ Specification,
+ Features,
+ Frontend,
+ Backend,
+ Close,
+ Payment,
+ Messaging,
+ Send,
+ Attachment,
+ Login,
+ Signup,
+ Empty,
+ ThreadClient,
+ ThreadProductOwner,
+};
diff --git a/src/components/Alert/index.tsx b/src/components/Alert/index.tsx
new file mode 100644
index 0000000..eb637c3
--- /dev/null
+++ b/src/components/Alert/index.tsx
@@ -0,0 +1,20 @@
+import { Wrapper } from './styles';
+
+type AlertProps = {
+ className?: string;
+ color:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error';
+ text: string;
+};
+
+const Alert = ({ text, ...props }: AlertProps) => {
+ return {text};
+};
+
+export default Alert;
diff --git a/src/components/Alert/styles.ts b/src/components/Alert/styles.ts
new file mode 100644
index 0000000..c1ace9e
--- /dev/null
+++ b/src/components/Alert/styles.ts
@@ -0,0 +1,72 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error';
+};
+
+export const Wrapper = styled.div`
+ width: 100%;
+ height: auto;
+ padding: 0.938rem;
+ border-radius: 10px;
+
+ ${({ color, theme }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ border: 1px solid ${theme.colors.client.main};
+ color: ${theme.colors.client.main};
+ background: ${theme.colors.client.light};
+ `;
+ case 'productOwner':
+ return css`
+ border: 1px solid ${theme.colors.productOwner.main};
+ color: ${theme.colors.productOwner.main};
+ background: ${theme.colors.productOwner.light};
+ `;
+ case 'developer':
+ return css`
+ border: 1px solid ${theme.colors.developer.main};
+ color: ${theme.colors.developer.main};
+ background: ${theme.colors.developer.light};
+ `;
+ case 'admin':
+ return css`
+ border: 1px solid ${theme.colors.admin.main};
+ color: ${theme.colors.admin.main};
+ background: ${theme.colors.admin.light};
+ `;
+ case 'success':
+ return css`
+ border: 1px solid ${theme.colors.success.main};
+ color: ${theme.colors.success.main};
+ background: ${theme.colors.success.light};
+ `;
+ case 'warning':
+ return css`
+ border: 1px solid ${theme.colors.warning.main};
+ color: ${theme.colors.warning.main};
+ background: ${theme.colors.warning.light};
+ `;
+ case 'error':
+ return css`
+ border: 1px solid ${theme.colors.error.main};
+ color: ${theme.colors.error.main};
+ background: ${theme.colors.error.light};
+ `;
+ default:
+ return css`
+ border: 1px solid ${theme.colors.client.main};
+ color: ${theme.colors.client.main};
+ background: ${theme.colors.client.light};
+ `;
+ }
+ }}
+`;
diff --git a/src/components/Avatar/index.tsx b/src/components/Avatar/index.tsx
new file mode 100644
index 0000000..cda51a0
--- /dev/null
+++ b/src/components/Avatar/index.tsx
@@ -0,0 +1,18 @@
+import { Wrapper } from './styles';
+
+type AvatarProps = {
+ className?: string;
+ color?: 'client' | 'productOwner' | 'developer' | 'admin' | string;
+ size?: 'small' | 'big';
+ text: string;
+};
+
+const Avatar = ({ color, size = 'small', text, className }: AvatarProps) => {
+ return (
+
+ {text}
+
+ );
+};
+
+export default Avatar;
diff --git a/src/components/Avatar/styles.ts b/src/components/Avatar/styles.ts
new file mode 100644
index 0000000..aa33bb6
--- /dev/null
+++ b/src/components/Avatar/styles.ts
@@ -0,0 +1,41 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin' | string;
+ size?: 'small' | 'big';
+};
+
+export const Wrapper = styled.div`
+ user-select: none;
+ border-radius: 50%;
+ background: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ color: ${({ theme }) => theme.colors.white.main};
+ display: inline-grid;
+ justify-content: center;
+ align-items: center;
+ font-weight: bold;
+
+ ${({ size }) => {
+ switch (size) {
+ case 'small':
+ return css`
+ width: 25px;
+ height: 25px;
+ font-size: 12px;
+ `;
+ case 'big':
+ return css`
+ width: 50px;
+ height: 50px;
+ font-size: 24px;
+ `;
+ default:
+ return css`
+ width: 25px;
+ height: 25px;
+ font-size: 12px;
+ `;
+ }
+ }}
+`;
diff --git a/src/components/BackendFeatureCard/index.tsx b/src/components/BackendFeatureCard/index.tsx
new file mode 100644
index 0000000..f23f512
--- /dev/null
+++ b/src/components/BackendFeatureCard/index.tsx
@@ -0,0 +1,36 @@
+import { Box, Text } from '..';
+import { Backend } from '../../assets';
+import { FeatureOutput } from '../../graphql/types';
+
+type BackendFeatureCardProps = {
+ feature: FeatureOutput;
+};
+
+const BackendFeatureCard = ({ feature }: BackendFeatureCardProps) => {
+ return (
+
+
+
+
+ {feature.name}
+
+
+
+
+
+
+
+ );
+};
+
+export default BackendFeatureCard;
diff --git a/src/components/Box/index.tsx b/src/components/Box/index.tsx
new file mode 100644
index 0000000..0fd30d9
--- /dev/null
+++ b/src/components/Box/index.tsx
@@ -0,0 +1,99 @@
+import React from 'react';
+import { Wrapper } from './styles';
+
+export type BoxProps = {
+ className?: string;
+ children?: React.ReactNode | JSX.Element | string;
+
+ onClick?: () => void;
+ cursor?: 'pointer' | 'default';
+
+ position?: 'static' | 'relative' | 'absolute' | 'fixed' | 'sticky';
+ zIndex?: string;
+ top?: string;
+ right?: string;
+ bottom?: string;
+ left?: string;
+
+ transformOrigin?: string;
+ transform?: string;
+
+ display?: 'none' | 'block' | 'inline' | 'inline-block' | 'flex' | 'grid';
+
+ flex?: string;
+ flexDirection?: 'row' | 'column';
+ flexWrap?: 'wrap' | 'unwrap';
+ flexGrow?: string;
+ flexShrink?: string;
+ order?: string;
+
+ gridRow?: string;
+ gridColumn?: string;
+ gridTemplate?: string;
+ gridTemplateRows?: string;
+ gridTemplateColumns?: string;
+ gap?: string;
+ rowGap?: string;
+ columnGap?: string;
+
+ alignItems?: 'center' | 'flex-start' | 'flex-end' | 'stretch';
+ justifyContent?:
+ | 'center'
+ | 'flex-start'
+ | 'flex-end'
+ | 'space-between'
+ | 'space-around';
+ alignSelf?: 'center' | 'flex-start' | 'flex-end';
+ justifySelf?: 'center' | 'flex-start' | 'flex-end';
+
+ boxSizing?: 'content-box' | 'border-box';
+ width?: string;
+ minWidth?: string;
+ maxWidth?: string;
+ height?: string;
+ minHeight?: string;
+ maxHeight?: string;
+
+ margin?: string;
+ marginTop?: string;
+ marginRight?: string;
+ marginBottom?: string;
+ marginLeft?: string;
+ padding?: string;
+ paddingTop?: string;
+ paddingRight?: string;
+ paddingBottom?: string;
+ paddingLeft?: string;
+
+ overflow?: 'visible' | 'hidden' | 'scroll';
+ overflowX?: 'visible' | 'hidden' | 'scroll';
+ overflowY?: 'visible' | 'hidden' | 'scroll';
+
+ border?: string;
+ borderRadius?: string;
+ boxShadow?: string;
+
+ color?: string;
+ background?: string;
+
+ fontFamily?: string;
+ fontSize?: string;
+ fontWeight?: string;
+ fontStyle?: string;
+ lineHeight?: string;
+ letterSpacing?: string;
+ textAlign?: 'center' | 'left' | 'right';
+ textDecoration?: string;
+};
+
+const Box = React.forwardRef(
+ ({ children, ...props }, ref) => {
+ return (
+
+ {children}
+
+ );
+ }
+);
+
+export default Box;
diff --git a/src/components/Box/styles.ts b/src/components/Box/styles.ts
new file mode 100644
index 0000000..aa28827
--- /dev/null
+++ b/src/components/Box/styles.ts
@@ -0,0 +1,83 @@
+import styled from 'styled-components';
+import { BoxProps } from '.';
+
+export const Wrapper = styled.div`
+ ${({ cursor }) => cursor && `cursor: ${cursor}`};
+
+ ${({ position }) => position && `position: ${position}`};
+ ${({ zIndex }) => zIndex && `z-index: ${zIndex}`};
+ ${({ top }) => top && `top: ${top}`};
+ ${({ right }) => right && `right: ${right}`};
+ ${({ bottom }) => bottom && `bottom: ${bottom}`};
+ ${({ left }) => left && `left: ${left}`};
+
+ ${({ transformOrigin }) =>
+ transformOrigin && `transform-origin: ${transformOrigin}`};
+ ${({ transform }) => transform && `transform: ${transform}`};
+
+ ${({ display }) => display && `display: ${display}`};
+
+ ${({ flex }) => flex && `flex: ${flex}`};
+ ${({ flexDirection }) => flexDirection && `flex-direction: ${flexDirection}`};
+ ${({ flexWrap }) => flexWrap && `flex-wrap: ${flexWrap}`};
+ ${({ flexGrow }) => flexGrow && `flex-grow: ${flexGrow}`};
+ ${({ flexShrink }) => flexShrink && `flex-shrink: ${flexShrink}`};
+ ${({ order }) => order && `order: ${order}`};
+
+ ${({ gridRow }) => gridRow && `grid-row: ${gridRow}`};
+ ${({ gridColumn }) => gridColumn && `grid-column: ${gridColumn}`};
+ ${({ gridTemplate }) => gridTemplate && `grid-template: ${gridTemplate}`};
+ ${({ gridTemplateRows }) =>
+ gridTemplateRows && `grid-template-rows: ${gridTemplateRows}`};
+ ${({ gridTemplateColumns }) =>
+ gridTemplateColumns && `grid-template-columns: ${gridTemplateColumns}`};
+ ${({ gap }) => gap && `gap: ${gap}`};
+ ${({ rowGap }) => rowGap && `row-gap: ${rowGap}`};
+ ${({ columnGap }) => columnGap && `column-gap: ${columnGap}`};
+
+ ${({ alignItems }) => alignItems && `align-items: ${alignItems}`};
+ ${({ justifyContent }) =>
+ justifyContent && `justify-content: ${justifyContent}`};
+ ${({ alignSelf }) => alignSelf && `align-self: ${alignSelf}`};
+ ${({ justifySelf }) => justifySelf && `justify-self: ${justifySelf}`};
+
+ ${({ boxSizing }) => boxSizing && `box-sizing: ${boxSizing}`};
+ ${({ width }) => width && `width: ${width}`};
+ ${({ minWidth }) => minWidth && `min-width: ${minWidth}`};
+ ${({ maxWidth }) => maxWidth && `max-width: ${maxWidth}`};
+ ${({ height }) => height && `height: ${height}`};
+ ${({ minHeight }) => minHeight && `min-height: ${minHeight}`};
+ ${({ maxHeight }) => maxHeight && `max-height: ${maxHeight}`};
+
+ ${({ margin }) => margin && `margin: ${margin}`};
+ ${({ marginTop }) => marginTop && `margin-top: ${marginTop}`};
+ ${({ marginRight }) => marginRight && `margin-right: ${marginRight}`};
+ ${({ marginBottom }) => marginBottom && `margin-bottom: ${marginBottom}`};
+ ${({ marginLeft }) => marginLeft && `margin-left: ${marginLeft}`};
+ ${({ padding }) => padding && `padding: ${padding}`};
+ ${({ paddingTop }) => paddingTop && `padding-top: ${paddingTop}`};
+ ${({ paddingRight }) => paddingRight && `padding-right: ${paddingRight}`};
+ ${({ paddingBottom }) => paddingBottom && `padding-bottom: ${paddingBottom}`};
+ ${({ paddingLeft }) => paddingLeft && `padding-left: ${paddingLeft}`};
+
+ ${({ overflow }) => overflow && `overflow: ${overflow}`};
+ ${({ overflowX }) => overflowX && `overflow-x: ${overflowX}`};
+ ${({ overflowY }) => overflowY && `overflow-y: ${overflowY}`};
+
+ ${({ border }) => border && `border: ${border}`};
+ ${({ borderRadius }) => borderRadius && `border-radius: ${borderRadius}`};
+ ${({ boxShadow }) => boxShadow && `box-shadow: ${boxShadow}`};
+
+ ${({ color }) => color && `color: ${color}`};
+ ${({ background }) => background && `background: ${background}`};
+
+ ${({ fontFamily }) => fontFamily && `font-family: ${fontFamily}`};
+ ${({ fontSize }) => fontSize && `font-size: ${fontSize}`};
+ ${({ fontWeight }) => fontWeight && `font-weight: ${fontWeight}`};
+ ${({ fontStyle }) => fontStyle && `font-style: ${fontStyle}`};
+ ${({ lineHeight }) => lineHeight && `line-height: ${lineHeight}`};
+ ${({ letterSpacing }) => letterSpacing && `letter-spacing: ${letterSpacing}`};
+ ${({ textAlign }) => textAlign && `text-align: ${textAlign}`};
+ ${({ textDecoration }) =>
+ textDecoration && `text-decoration: ${textDecoration}`};
+`;
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
new file mode 100644
index 0000000..8fadab5
--- /dev/null
+++ b/src/components/Button/index.tsx
@@ -0,0 +1,56 @@
+import { Spinner } from '..';
+import { Wrapper } from './styles';
+
+type ButtonProps = {
+ color: 'client' | 'productOwner' | 'developer' | 'admin' | 'error';
+ size?: 'small' | 'big';
+ variant?: 'primary-action' | 'secondary-action' | 'outlined' | 'text';
+ type?: 'submit' | 'button' | 'reset';
+ iconLeft?: React.FunctionComponentElement>;
+ iconRight?: React.FunctionComponentElement>;
+ fullWidth?: boolean;
+ loading?: boolean;
+ disabled?: boolean;
+ text: string;
+ onClick?: () => void;
+};
+
+const Button = ({
+ color,
+ size = 'small',
+ variant = 'text',
+ type = 'button',
+ iconLeft,
+ iconRight,
+ fullWidth = false,
+ loading = false,
+ disabled = false,
+ text,
+ onClick,
+}: ButtonProps) => {
+ return (
+
+ {iconLeft && {iconLeft}}
+ {text}
+ {iconRight && !loading && {iconRight}}
+ {loading && (
+
+
+
+ )}
+
+ );
+};
+
+export default Button;
diff --git a/src/components/Button/styles.ts b/src/components/Button/styles.ts
new file mode 100644
index 0000000..dc1e694
--- /dev/null
+++ b/src/components/Button/styles.ts
@@ -0,0 +1,231 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color: 'client' | 'productOwner' | 'developer' | 'admin' | 'error';
+ size?: 'small' | 'big';
+ variant?: 'primary-action' | 'secondary-action' | 'outlined' | 'text';
+ iconLeft?: React.FunctionComponentElement>;
+ iconRight?: React.FunctionComponentElement>;
+ load?: boolean;
+ disabled?: boolean;
+ fullWidth?: boolean;
+};
+
+export const Wrapper = styled.button`
+ cursor: pointer;
+ outline: none;
+ border: none;
+ border-radius: 6px;
+ background: none;
+ font-weight: bold;
+
+ .icon svg {
+ display: flex;
+ align-items: center;
+ }
+
+ ${({ iconLeft, iconRight, load }) => {
+ if (iconLeft || iconRight || load)
+ return css`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ `;
+ return '';
+ }}
+
+ .icon {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .icon.left {
+ margin-right: 0.5rem;
+ }
+
+ .icon.right {
+ margin-left: 0.5rem;
+ }
+
+ .lds-dual-ring {
+ display: inline !important;
+
+ border-width: 2px !important;
+ }
+
+ ${({ size }) => {
+ switch (size) {
+ case 'small':
+ return css`
+ padding: 0.625rem 1.875rem;
+ font-size: 1rem;
+
+ .icon svg {
+ width: 1rem;
+ height: 1rem;
+ }
+
+ .lds-dual-ring {
+ width: 1rem !important;
+ height: 1rem !important;
+
+ &:after {
+ width: 0.5rem !important;
+ height: 0.5rem !important;
+ }
+ }
+ `;
+ case 'big':
+ return css`
+ padding: 0.625rem 1.875rem;
+ font-size: 1.25rem;
+
+ .icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+
+ .lds-dual-ring {
+ width: 1.25rem !important;
+ height: 1.25rem !important;
+
+ &:after {
+ width: 0.75rem !important;
+ height: 0.75rem !important;
+ }
+ }
+ `;
+ default:
+ return css`
+ padding: 0.625rem 1.875rem;
+ font-size: 1rem;
+
+ .icon svg {
+ width: 1rem;
+ height: 1rem;
+ }
+
+ .lds-dual-ring {
+ width: 1rem !important;
+ height: 1rem !important;
+
+ &:after {
+ width: 0.5rem !important;
+ height: 0.5rem !important;
+ }
+ }
+ `;
+ }
+ }}
+
+ ${({ fullWidth }) =>
+ fullWidth &&
+ css`
+ width: 100%;
+ font-size: 1.25rem;
+
+ .icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+
+ .lds-dual-ring {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+ `};
+
+ ${({ variant, color, theme, disabled }) => {
+ switch (variant) {
+ case 'primary-action':
+ return css`
+ background: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ color: ${theme.colors.white.main};
+
+ .icon svg path {
+ stroke: ${theme.colors.white.main};
+ }
+
+ &:hover {
+ background: ${!disabled
+ ? theme.colors[color].dark
+ : theme.colors[color].light};
+ }
+ `;
+ case 'secondary-action':
+ return css`
+ background: ${theme.colors[color].light};
+ color: ${!disabled ? '#262628' : theme.colors[color].light};
+
+ .icon svg path {
+ stroke: ${!disabled ? '#262628' : theme.colors[color].light};
+ }
+ `;
+ case 'outlined':
+ return css`
+ background: none;
+ color: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ border: 2px solid
+ ${!disabled ? theme.colors[color].main : theme.colors[color].light};
+
+ .icon svg path {
+ stroke: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ }
+
+ &:hover {
+ background: ${!disabled ? theme.colors[color].main : 'none'};
+ color: ${!disabled
+ ? theme.colors.white.main
+ : theme.colors[color].light};
+
+ .icon svg path {
+ stroke: ${!disabled
+ ? theme.colors.white.main
+ : theme.colors[color].light};
+ }
+ }
+ `;
+ case 'text':
+ return css`
+ background: none;
+ color: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ padding: 0;
+
+ .icon svg path {
+ stroke: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ }
+ `;
+ default:
+ return css`
+ background: none;
+ color: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ padding: 0;
+
+ .icon svg path {
+ stroke: ${!disabled
+ ? theme.colors[color].main
+ : theme.colors[color].light};
+ }
+ `;
+ }
+ }}
+
+ ${({ disabled }) =>
+ disabled &&
+ css`
+ cursor: default;
+ `}
+`;
diff --git a/src/components/CategoryCard/index.tsx b/src/components/CategoryCard/index.tsx
new file mode 100644
index 0000000..44d9eff
--- /dev/null
+++ b/src/components/CategoryCard/index.tsx
@@ -0,0 +1,50 @@
+import { Box, Text } from '..';
+import { CategoryOutput } from '../../graphql/types';
+import { theme } from '../../themes';
+
+type CategoryCardProps = {
+ category: CategoryOutput;
+ selectable?: boolean;
+ selected?: boolean;
+ toggleSelect?: () => void;
+ color: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+const CategoryCard = ({
+ category,
+ selectable = false,
+ selected = false,
+ toggleSelect = () => {},
+ color,
+}: CategoryCardProps) => {
+ return (
+ {}}
+ display='grid'
+ gridTemplateRows='auto'
+ alignItems='center'
+ rowGap='10px'
+ borderRadius='10px'
+ cursor='pointer'
+ >
+
+
+
+ {category.name}
+
+
+
+
+
+ {category.description}
+
+
+
+ );
+};
+
+export default CategoryCard;
diff --git a/src/components/CheckBox/index.tsx b/src/components/CheckBox/index.tsx
new file mode 100644
index 0000000..776a67b
--- /dev/null
+++ b/src/components/CheckBox/index.tsx
@@ -0,0 +1,31 @@
+import { Wrapper } from './styles';
+import { Text } from '..';
+import { Check } from '../../assets';
+
+type CheckBoxProps = {
+ className?: string;
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ label: string;
+ name: string;
+ checked: boolean;
+ onClick: () => void;
+};
+
+const CheckBox = ({
+ label,
+ name,
+ checked,
+ onClick,
+ ...props
+}: CheckBoxProps) => {
+ return (
+
+
+
+
+ {label}
+
+ );
+};
+
+export default CheckBox;
diff --git a/src/components/CheckBox/styles.ts b/src/components/CheckBox/styles.ts
new file mode 100644
index 0000000..28cfdcb
--- /dev/null
+++ b/src/components/CheckBox/styles.ts
@@ -0,0 +1,74 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ checked: boolean;
+};
+
+export const Wrapper = styled.div`
+ display: inline-flex;
+ flex-direction: row;
+ user-select: none;
+
+ .checkbox {
+ cursor: pointer;
+ border-radius: 3px;
+ margin-right: 10px;
+ width: 17px;
+ height: 17px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ visibility: ${({ checked }) => (checked ? 'visible' : 'hidden')};
+ }
+ }
+
+ ${({ checked, color, theme }) => {
+ if (!checked)
+ return css`
+ .checkbox {
+ border: 2px solid ${theme.colors.black.main};
+ background: ${theme.colors.white.main};
+ }
+ `;
+ switch (color) {
+ case 'client':
+ return css`
+ .checkbox {
+ border: none;
+ background: ${theme.colors.client.main};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ .checkbox {
+ border: none;
+ background: ${theme.colors.productOwner.main};
+ }
+ `;
+ case 'developer':
+ return css`
+ .checkbox {
+ border: none;
+ background: ${theme.colors.developer.main};
+ }
+ `;
+ case 'admin':
+ return css`
+ .checkbox {
+ border: none;
+ background: ${theme.colors.admin.main};
+ }
+ `;
+ default:
+ return css`
+ .checkbox {
+ border: none;
+ background: ${theme.colors.client.main};
+ }
+ `;
+ }
+ }}
+`;
diff --git a/src/components/Chip/index.tsx b/src/components/Chip/index.tsx
new file mode 100644
index 0000000..fbbb20f
--- /dev/null
+++ b/src/components/Chip/index.tsx
@@ -0,0 +1,27 @@
+import { Wrapper } from './styles';
+import { Text } from '..';
+
+type ChipProps = {
+ variant?: 'outlined' | 'filled';
+ color:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error';
+ text: string;
+};
+
+const Chip = ({ variant = 'outlined', color, text }: ChipProps) => {
+ return (
+
+
+ {text}
+
+
+ );
+};
+
+export default Chip;
diff --git a/src/components/Chip/styles.ts b/src/components/Chip/styles.ts
new file mode 100644
index 0000000..16245d7
--- /dev/null
+++ b/src/components/Chip/styles.ts
@@ -0,0 +1,32 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error';
+ variant: 'outlined' | 'filled';
+};
+
+export const Wrapper = styled.div`
+ padding: 5px 15px;
+ border-radius: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ ${({ variant, color, theme }) =>
+ variant === 'outlined'
+ ? css`
+ border: 2px solid ${theme.colors[color].main};
+ color: ${theme.colors[color].main};
+ `
+ : css`
+ background: ${theme.colors[color].main};
+ color: ${theme.colors.white.main};
+ `}
+`;
diff --git a/src/components/ContextMenu/index.tsx b/src/components/ContextMenu/index.tsx
new file mode 100644
index 0000000..91296f7
--- /dev/null
+++ b/src/components/ContextMenu/index.tsx
@@ -0,0 +1,63 @@
+import { useEffect, useRef, useState } from 'react';
+import { Wrapper } from './styles';
+import { Text } from '..';
+
+type ContextMenuProps = {
+ className?: string;
+ items: Array<{ label: string; action?: () => void }>;
+ component: string;
+};
+
+const ContextMenu = ({ items, component, className }: ContextMenuProps) => {
+ const [open, setOpen] = useState(false);
+ const parentComponentRef = useRef();
+
+ useEffect(() => {
+ parentComponentRef.current = document.querySelector(`#${component}`) as HTMLDivElement;
+
+ const openMenu = () => setOpen(true);
+ const closeMenu = () => setOpen(false);
+
+ parentComponentRef.current?.addEventListener(
+ 'mouseenter',
+ openMenu
+ );
+ parentComponentRef.current?.addEventListener(
+ 'mouseleave',
+ closeMenu
+ );
+
+ return () => {
+ parentComponentRef.current?.removeEventListener('mouseenter', openMenu);
+ parentComponentRef.current?.removeEventListener('mouseleave', closeMenu);
+ };
+ }, []);
+
+ return (
+
+ {open && (
+
+ {items.map(({ label, action }) => (
+ - {
+ if (action) {
+ setOpen(false);
+ action();
+ }
+ }}
+ key={label}
+ >
+ {label}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default ContextMenu;
diff --git a/src/components/ContextMenu/styles.ts b/src/components/ContextMenu/styles.ts
new file mode 100644
index 0000000..429052d
--- /dev/null
+++ b/src/components/ContextMenu/styles.ts
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ top: number;
+ left: number;
+};
+
+export const Wrapper = styled.div`
+ ul {
+ position: fixed;
+ top: ${({ top }) => top}px;
+ left: ${({ left }) => left}px;
+ background: #1f1b1b;
+ display: grid;
+ grid-template-columns: auto;
+ row-gap: 0.5rem;
+ color: ${({ theme }) => theme.colors.white.main};
+ border-radius: 3px;
+ padding: 5px 20px 5px 10px;
+
+ li {
+ cursor: pointer;
+ }
+ }
+`;
diff --git a/src/components/FeatureCard/index.tsx b/src/components/FeatureCard/index.tsx
new file mode 100644
index 0000000..2f2a8b2
--- /dev/null
+++ b/src/components/FeatureCard/index.tsx
@@ -0,0 +1,72 @@
+import { Box, Text } from '..';
+import { Backend, Frontend } from '../../assets';
+import { FeatureOutput } from '../../graphql/types';
+import { theme } from '../../themes';
+
+type FeatureCardProps = {
+ feature: FeatureOutput;
+ selectable?: boolean;
+ selected?: boolean;
+ toggleSelect?: () => void;
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+const FeatureCard = ({
+ feature,
+ selectable = false,
+ selected = false,
+ toggleSelect = () => {},
+ color,
+}: FeatureCardProps) => {
+ return (
+ {}}
+ display='grid'
+ gridTemplateRows='auto'
+ alignItems='center'
+ rowGap='10px'
+ borderRadius='10px'
+ cursor={selectable ? 'pointer' : undefined}
+ >
+
+
+
+ {feature.name}
+
+
+
+
+ {feature.featureType === 'frontend' ||
+ (feature.featureType === 'fullstack' && )}
+
+
+ {feature.featureType === 'backend' ||
+ (feature.featureType === 'fullstack' && )}
+
+
+
+
+
+ {feature.description}
+
+
+ ${feature.price}
+
+
+
+ );
+};
+
+export default FeatureCard;
diff --git a/src/components/FrontendFeatureCard/index.tsx b/src/components/FrontendFeatureCard/index.tsx
new file mode 100644
index 0000000..3893a19
--- /dev/null
+++ b/src/components/FrontendFeatureCard/index.tsx
@@ -0,0 +1,61 @@
+import { Handle, Position } from 'reactflow';
+import { Box, Text } from '..';
+import { FeatureOutput } from '../../graphql/types';
+
+type FrontendFeatureCardProps = {
+ data: FeatureOutput;
+ isConnectable?: boolean;
+ className?: string;
+};
+
+const FrontendFeatureCard = ({
+ data,
+ isConnectable = false,
+ className,
+}: FrontendFeatureCardProps) => {
+ return (
+ <>
+
+
+
+
+
+ {data.name}
+
+
+
+
+ {data.wireframes?.map((wireframe) => (
+
+ ))}
+
+
+
+ >
+ );
+};
+
+export default FrontendFeatureCard;
diff --git a/src/components/IconButton/index.tsx b/src/components/IconButton/index.tsx
new file mode 100644
index 0000000..3ca7a2c
--- /dev/null
+++ b/src/components/IconButton/index.tsx
@@ -0,0 +1,23 @@
+import { Wrapper } from './styles';
+
+type IconButtonProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ size?: 'small' | 'medium' | 'big';
+ icon?: React.FunctionComponentElement>;
+ onClick: () => void;
+};
+
+const IconButton = ({
+ color,
+ size = 'medium',
+ icon,
+ onClick,
+}: IconButtonProps) => {
+ return (
+
+ {icon}
+
+ );
+};
+
+export default IconButton;
diff --git a/src/components/IconButton/styles.ts b/src/components/IconButton/styles.ts
new file mode 100644
index 0000000..0e97323
--- /dev/null
+++ b/src/components/IconButton/styles.ts
@@ -0,0 +1,75 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ size?: 'small' | 'medium' | 'big';
+ icon?: React.FunctionComponentElement>;
+};
+
+export const Wrapper = styled.button`
+ cursor: pointer;
+ outline: none;
+ border: none;
+ border-radius: 50%;
+ background: none;
+ font-weight: bold;
+ background: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ display: grid;
+ justify-content: center;
+ align-items: center;
+
+ svg {
+ display: flex;
+ align-items: center;
+
+ path {
+ stroke: ${({ theme }) => theme.colors.white.main};
+ }
+ }
+
+ ${({ size }) => {
+ switch (size) {
+ case 'small':
+ return css`
+ width: 25px;
+ height: 25px;
+
+ svg {
+ width: 12.5px;
+ height: 12.5px;
+ }
+ `;
+ case 'medium':
+ return css`
+ width: 35px;
+ height: 35px;
+
+ svg {
+ width: 17.5px;
+ height: 17.5px;
+ }
+ `;
+ case 'big':
+ return css`
+ width: 50px;
+ height: 50px;
+
+ svg {
+ width: 24.5px;
+ height: 24.5px;
+ }
+ `;
+ default:
+ return css`
+ width: 25px;
+ height: 25px;
+
+ svg {
+ width: 12.5px;
+ height: 12.5px;
+ }
+ `;
+ }
+ }}
+`;
diff --git a/src/components/ImagePreview/index.tsx b/src/components/ImagePreview/index.tsx
new file mode 100644
index 0000000..9656dae
--- /dev/null
+++ b/src/components/ImagePreview/index.tsx
@@ -0,0 +1,53 @@
+import { Wrapper } from './styles';
+import { Upload, Close } from '../../assets';
+
+type ImagePreviewProps = {
+ className?: string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ name?: string;
+ image: { name: string; src: string } | undefined;
+ deletable?: boolean;
+ onChange?: (event: React.ChangeEvent) => void;
+ onDelete?: () => void;
+};
+
+const ImagePreview = ({
+ name,
+ image,
+ deletable = false,
+ onChange,
+ onDelete,
+ ...props
+}: ImagePreviewProps) => {
+ return (
+
+ {image ? (
+
+ {deletable && (
+
+
+
+ )}
+
+ ) : (
+
+
+
+
+ )}
+
+ );
+};
+
+export default ImagePreview;
diff --git a/src/components/ImagePreview/styles.ts b/src/components/ImagePreview/styles.ts
new file mode 100644
index 0000000..755f689
--- /dev/null
+++ b/src/components/ImagePreview/styles.ts
@@ -0,0 +1,99 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ error?: boolean;
+ deletable?: boolean;
+ image: { name: string; src: string } | undefined;
+};
+
+export const Wrapper = styled.div`
+ .preview {
+ width: 175px;
+ height: 325px;
+ background: url(${({ image }) => image?.src});
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: center;
+ padding: 150px 30px;
+ position: relative;
+
+ &:hover {
+ ${({ deletable, color, theme }) =>
+ deletable &&
+ css`
+ border: 2px solid ${theme.colors[color || 'client'].main};
+ `}
+
+ .close {
+ display: block;
+ }
+ }
+
+ .close {
+ background: ${({ color, theme }) => theme.colors[color || 'client'].main};
+ border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: absolute;
+ top: -11.5px;
+ right: -11.5px;
+ padding: 5px;
+ cursor: pointer;
+ display: none;
+
+ svg {
+ width: 15px;
+ height: 15px;
+ stroke: ${({ theme }) => theme.colors.white.main};
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+ }
+
+ .upload {
+ padding: 150px 30px;
+ position: relative;
+ border: 2px solid
+ ${({ color, theme }) => theme.colors[color || 'client'].main};
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ input {
+ opacity: 0;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+
+ &::-webkit-file-upload-button {
+ display: none;
+ }
+ }
+
+ svg {
+ width: 25px;
+ height: 25px;
+
+ path {
+ stroke: ${({ color, theme }) => theme.colors[color || 'client'].main};
+ }
+ }
+ }
+`;
diff --git a/src/components/Input/index.tsx b/src/components/Input/index.tsx
new file mode 100644
index 0000000..d4521c4
--- /dev/null
+++ b/src/components/Input/index.tsx
@@ -0,0 +1,80 @@
+import { Text } from '..';
+import { Upload } from '../../assets';
+import { Wrapper } from './styles';
+
+type InputProps = {
+ className?: string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ value?: string | number;
+ label?: string;
+ name?: string;
+ type?: 'text' | 'email' | 'tel' | 'password' | 'file' | 'number';
+ file?: boolean;
+ placeholder?: string;
+ fullWidth?: boolean;
+ onChange: (event: React.ChangeEvent) => void;
+ onBlur?: (event: React.FocusEvent) => void;
+};
+
+const Input = ({
+ type = 'text',
+ file = false,
+ color = 'client',
+ label,
+ name,
+ placeholder,
+ value,
+ onChange,
+ onBlur,
+ error,
+ errorMessage,
+ ...props
+}: InputProps) => {
+ return (
+
+
+ {label && (
+
+ {label}
+
+ )}
+ {error && errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+
+ {type === 'file' && (
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default Input;
diff --git a/src/components/Input/styles.ts b/src/components/Input/styles.ts
new file mode 100644
index 0000000..5dda676
--- /dev/null
+++ b/src/components/Input/styles.ts
@@ -0,0 +1,297 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'gray'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ type?: 'text' | 'email' | 'tel' | 'password' | 'file' | 'number';
+ label?: string;
+ fullWidth?: boolean;
+};
+
+export const Wrapper = styled.div`
+ .input {
+ width: inherit;
+ height: inherit;
+ border-radius: 5px;
+ padding: 2px;
+ color: ${({ theme }) => theme.colors.black.main};
+
+ div {
+ background: ${({ theme }) => theme.colors.white.main};
+ padding: 1rem;
+ border-radius: 5px;
+ }
+ }
+
+ .info {
+ margin-bottom: 5px;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+
+ p {
+ background: ${({ theme }) => theme.colors.gray.dark};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .label {
+ justify-self: flex-start;
+ }
+
+ .error-message {
+ justify-self: flex-end;
+ }
+ }
+
+ input {
+ width: 100%;
+ background: none;
+ border: none;
+ color: ${({ theme }) => theme.colors.black.main};
+ }
+
+ input[type='file'] {
+ cursor: pointer;
+
+ &::-webkit-file-upload-button {
+ display: none;
+ }
+ }
+
+ ${({ type }) => {
+ if (type === 'file')
+ return css`
+ .input div {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+ `;
+ return '';
+ }}
+
+ .icon {
+ ${({ type }) => type === 'file' && `cursor: pointer`};
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .icon.left {
+ margin-right: 0.5rem;
+ }
+
+ ${({ color, theme }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ .input {
+ background: ${theme.colors.client.light};
+
+ &:focus-within {
+ background: ${theme.colors.client.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.client.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.client.main};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ .input {
+ background: ${theme.colors.productOwner.light};
+
+ &:focus-within {
+ background: ${theme.colors.productOwner.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.productOwner.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.productOwner.main};
+ }
+ `;
+ case 'developer':
+ return css`
+ .input {
+ background: ${theme.colors.developer.light};
+
+ &:focus-within {
+ background: ${theme.colors.developer.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.developer.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.developer.main};
+ }
+ `;
+ case 'admin':
+ return css`
+ .input {
+ background: ${theme.colors.admin.light};
+
+ &:focus-within {
+ background: ${theme.colors.admin.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.admin.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.admin.main};
+ }
+ `;
+ case 'success':
+ return css`
+ .input {
+ background: ${theme.colors.success.main};
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.success.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.success.main};
+ }
+ `;
+ case 'warning':
+ return css`
+ .input {
+ background: ${theme.colors.warning.main};
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.warning.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.warning.main};
+ }
+ `;
+ case 'error':
+ return css`
+ .input {
+ background: ${theme.colors.error.main};
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.error.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.error.main};
+ }
+ `;
+ case 'black':
+ return css`
+ .input {
+ background: ${theme.colors.black.main};
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.black.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.black.main};
+ }
+ `;
+ case 'white':
+ return css`
+ .input {
+ background: ${theme.colors.white.main};
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.white.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.white.main};
+ }
+ `;
+ default:
+ return css`
+ .input {
+ background: ${theme.colors.client.light};
+
+ &:focus-within {
+ background: ${theme.colors.client.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.client.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.client.main};
+ }
+ `;
+ }
+ }}
+
+ ${({ error, theme }) =>
+ error &&
+ css`
+ .info p {
+ background: ${theme.colors.error.main};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .input {
+ background: ${theme.colors.error.main};
+
+ &:focus-within {
+ background: ${theme.colors.error.main};
+ }
+ }
+
+ input[type='file'] {
+ color: ${theme.colors.error.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.error.main};
+ }
+ `}
+
+ ${({ fullWidth }) =>
+ fullWidth &&
+ css`
+ width: 100%;
+ font-size: 1.25rem;
+
+ .icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+ `};
+`;
diff --git a/src/components/Link/index.tsx b/src/components/Link/index.tsx
new file mode 100644
index 0000000..cdf1bd8
--- /dev/null
+++ b/src/components/Link/index.tsx
@@ -0,0 +1,52 @@
+import { Link as RouterLink } from 'react-router-dom';
+import { Wrapper } from './styles';
+
+type LinkProps = {
+ href?: string;
+ url?: boolean;
+ children?: React.ReactNode | JSX.Element | string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white'
+ | string;
+ selected?: boolean;
+ className?: string;
+ iconLeft?: React.FunctionComponentElement>;
+ onClick?: () => void;
+ target?: '_self' | '_blank';
+};
+
+const Link = ({
+ href,
+ url = false,
+ children,
+ iconLeft,
+ selected = false,
+ target = '_self',
+ ...props
+}: LinkProps) => {
+ return (
+
+ {href && !url ? (
+
+ {iconLeft && {iconLeft}}
+ {children}
+
+ ) : (
+
+ {iconLeft && {iconLeft}}
+ {children}
+
+ )}
+
+ );
+};
+
+export default Link;
diff --git a/src/components/Link/styles.ts b/src/components/Link/styles.ts
new file mode 100644
index 0000000..215f9fc
--- /dev/null
+++ b/src/components/Link/styles.ts
@@ -0,0 +1,230 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white'
+ | string;
+ selected: boolean;
+ iconLeft?: React.SVGProps;
+};
+
+export const Wrapper = styled.div`
+ display: inline;
+
+ a {
+ text-decoration: ${({ selected }) => (selected ? 'underline' : 'none')};
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ ${({ color, theme }) => {
+ if (!color)
+ return css`
+ color: #3e66fb;
+
+ a {
+ color: #3e66fb;
+ }
+
+ .icon svg path {
+ stroke: #3e66fb;
+ }
+
+ a:visited {
+ color: #3e66fb;
+ }
+ `;
+ switch (color) {
+ case 'client':
+ return css`
+ color: ${theme.colors.client.main};
+
+ a {
+ color: ${theme.colors.client.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.client.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.client.main};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ color: ${theme.colors.productOwner.main};
+
+ a {
+ color: ${theme.colors.productOwner.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.productOwner.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.productOwner.main};
+ }
+ `;
+ case 'developer':
+ return css`
+ color: ${theme.colors.developer.main};
+
+ a {
+ color: ${theme.colors.developer.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.developer.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.developer.main};
+ }
+ `;
+ case 'admin':
+ return css`
+ color: ${theme.colors.admin.main};
+
+ a {
+ color: ${theme.colors.admin.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.admin.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.admin.main};
+ }
+ `;
+ case 'success':
+ return css`
+ color: ${theme.colors.success.main};
+
+ a {
+ color: ${theme.colors.success.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.success.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.success.main};
+ }
+ `;
+ case 'warning':
+ return css`
+ color: ${theme.colors.warning.main};
+
+ a {
+ color: ${theme.colors.warning.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.warning.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.warning.main};
+ }
+ `;
+ case 'error':
+ return css`
+ color: ${theme.colors.error.main};
+
+ a {
+ color: ${theme.colors.error.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.error.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.error.main};
+ }
+ `;
+ case 'black':
+ return css`
+ color: ${theme.colors.black.main};
+
+ a {
+ color: ${theme.colors.black.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.black.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.black.main};
+ }
+ `;
+ case 'white':
+ return css`
+ color: ${theme.colors.white.main};
+
+ a {
+ color: ${theme.colors.white.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.white.main};
+ }
+
+ a:visited {
+ color: ${theme.colors.white.main};
+ }
+ `;
+ default:
+ return css`
+ color: ${color};
+
+ a {
+ color: ${color};
+ }
+
+ .icon svg path {
+ stroke: ${color};
+ }
+
+ a:visited {
+ color: ${color};
+ }
+ `;
+ }
+ }}
+
+ ${({ iconLeft }) => {
+ if (iconLeft)
+ return css`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ `;
+ return '';
+ }}
+
+ .icon {
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .icon.left {
+ margin-right: 5px;
+ }
+`;
diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx
new file mode 100644
index 0000000..127981f
--- /dev/null
+++ b/src/components/Menu/index.tsx
@@ -0,0 +1,69 @@
+import { useEffect, useRef, useState } from 'react';
+import { Wrapper } from './styles';
+import { Text } from '..';
+
+type MenuProps = {
+ className?: string;
+ items: Array<{
+ icon: React.FunctionComponentElement>;
+ avoid?: boolean;
+ label: string;
+ action?: () => void;
+ }>;
+ component: string;
+};
+
+const Menu = ({ items, component, className }: MenuProps) => {
+ const [open, setOpen] = useState(false);
+ const componentRef = useRef(null);
+ const parentComponentRef = useRef();
+
+ const openMenu = () => setOpen(true);
+ const closeMenu = () => setOpen(false);
+
+ useEffect(() => {
+ parentComponentRef.current = document.querySelector(`#${component}`) as HTMLDivElement;
+
+ parentComponentRef.current?.addEventListener('mouseenter', openMenu);
+ componentRef.current?.addEventListener('mouseleave', closeMenu);
+
+ return () => {
+ parentComponentRef.current?.removeEventListener('mouseenter', openMenu);
+ componentRef.current?.removeEventListener('mouseleave', closeMenu);
+ };
+ }, []);
+
+ return (
+
+ {open && (
+
+ {items.map(({ icon, label, avoid, action }) => (
+ - {
+ if (action) {
+ setOpen(false);
+ action();
+ }
+ }}
+ key={label}
+ >
+ {icon}
+ {label}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default Menu;
diff --git a/src/components/Menu/styles.ts b/src/components/Menu/styles.ts
new file mode 100644
index 0000000..caa24ed
--- /dev/null
+++ b/src/components/Menu/styles.ts
@@ -0,0 +1,43 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ top: number;
+ left: number;
+};
+
+export const Wrapper = styled.div`
+ ul {
+ position: fixed;
+ top: ${({ top }) => top}px;
+ left: ${({ left }) => left}px;
+ background: ${({ theme }) => theme.colors.white.main};
+ display: grid;
+ grid-template-columns: auto;
+ row-gap: 0.5rem;
+ border-radius: 3px;
+ padding: 15px 30px 15px 15px;
+ box-shadow: 1px 1px 15px 0px rgba(50, 59, 105, 0.25);
+
+ li {
+ cursor: pointer;
+ display: grid;
+ grid-template-columns: 24px 1fr;
+ column-gap: 10px;
+ justify-content: flex-start;
+
+ .icon {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg path {
+ stroke: ${({ theme }) => theme.colors.black.main};
+ }
+
+ &.avoid svg path {
+ stroke: ${({ theme }) => theme.colors.error.main};
+ }
+ }
+ }
+ }
+`;
diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx
new file mode 100644
index 0000000..e916e94
--- /dev/null
+++ b/src/components/Modal/index.tsx
@@ -0,0 +1,54 @@
+import { theme } from '../../themes';
+import { Box, Button, Text } from '..';
+import { Wrapper } from './styles';
+
+type ModalProps = {
+ color: 'client' | 'productOwner' | 'developer' | 'admin';
+ title: string;
+ description: string;
+ children?: React.ReactNode | JSX.Element | string;
+ onConfirm: () => void;
+ onClose: () => void;
+};
+
+const Modal = ({
+ color,
+ title,
+ description,
+ children,
+ onConfirm,
+ onClose,
+}: ModalProps) => {
+ return (
+
+
+
+ {title}
+
+
+ {description}
+
+ {children}
+
+
+
+
+
+
+ );
+};
+
+export default Modal;
diff --git a/src/components/Modal/styles.ts b/src/components/Modal/styles.ts
new file mode 100644
index 0000000..c41cfc0
--- /dev/null
+++ b/src/components/Modal/styles.ts
@@ -0,0 +1,14 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+ position: fixed;
+ z-index: 200;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.15);
+ display: grid;
+ align-items: center;
+ justify-content: center;
+`;
diff --git a/src/components/Navbar/index.tsx b/src/components/Navbar/index.tsx
new file mode 100644
index 0000000..935372d
--- /dev/null
+++ b/src/components/Navbar/index.tsx
@@ -0,0 +1,172 @@
+import { useReactiveVar } from '@apollo/client';
+import { useNavigate, useLocation } from 'react-router';
+import { roleVar, tokenVar, userVar } from '../../graphql/state';
+import { Wrapper } from './styles';
+import { Avatar, Link, Menu, Text } from '..';
+import { Settings, Logout, Logo } from '../../assets';
+
+const Navbar = () => {
+ const user = useReactiveVar(userVar);
+ const role = useReactiveVar(roleVar);
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ return (
+
+
+
+
+
+
+
+
+
+ {user?.firstName} {user?.lastName}
+
+
+ ,
+ label: 'Settings',
+ action: () => navigate('/settings'),
+ },
+ {
+ icon: ,
+ label: 'Logout',
+ action: () => {
+ tokenVar(undefined);
+ localStorage.removeItem('token');
+ navigate('/login');
+ },
+ avoid: true,
+ },
+ ]}
+ />
+
+ );
+};
+
+export default Navbar;
diff --git a/src/components/Navbar/styles.ts b/src/components/Navbar/styles.ts
new file mode 100644
index 0000000..a2d1530
--- /dev/null
+++ b/src/components/Navbar/styles.ts
@@ -0,0 +1,47 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+export const Wrapper = styled.div`
+ background: ${({ theme }) => theme.colors.white.main};
+ box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.25);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 15px 45px 15px 120px;
+ user-select: none;
+ position: sticky;
+ top: 0;
+ z-index: 99;
+
+ svg {
+ display: flex;
+ align-items: center;
+ }
+
+ .logo-icon {
+ fill: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ }
+
+ nav {
+ flex-grow: 1;
+ margin-left: 60px;
+ display: grid;
+ grid-template-columns: repeat(4, auto);
+ column-gap: 20px;
+ justify-content: flex-start;
+ }
+
+ .user {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ p {
+ margin-left: 5px;
+ }
+ }
+`;
diff --git a/src/components/Protected/index.tsx b/src/components/Protected/index.tsx
new file mode 100644
index 0000000..04abfe3
--- /dev/null
+++ b/src/components/Protected/index.tsx
@@ -0,0 +1,19 @@
+import { useReactiveVar } from '@apollo/client';
+import { Navigate } from 'react-router-dom';
+import { tokenVar } from '../../graphql/state';
+
+type Props = {
+ children: React.ReactNode;
+};
+
+const Protected = ({ children }: Props) => {
+ const token = useReactiveVar(tokenVar);
+
+ return (
+ <>
+ {token ? children : }
+ >
+ );
+};
+
+export default Protected;
diff --git a/src/components/Public/index.tsx b/src/components/Public/index.tsx
new file mode 100644
index 0000000..fac62d5
--- /dev/null
+++ b/src/components/Public/index.tsx
@@ -0,0 +1,19 @@
+import { useReactiveVar } from '@apollo/client';
+import { Navigate } from 'react-router-dom';
+import { tokenVar } from '../../graphql/state';
+
+type Props = {
+ children: React.ReactNode;
+};
+
+const Public = ({ children }: Props) => {
+ const token = useReactiveVar(tokenVar);
+
+ return (
+ <>
+ {!token ? children : }
+ >
+ );
+};
+
+export default Public;
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
new file mode 100644
index 0000000..19b3a0a
--- /dev/null
+++ b/src/components/Search/index.tsx
@@ -0,0 +1,46 @@
+import { Wrapper } from './styles';
+import { Search as SearchIcon } from '../../assets';
+
+type SearchProps = {
+ className?: string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ value: string;
+ fullWidth?: boolean;
+ onChange: (event: React.ChangeEvent) => void;
+};
+
+const Search = ({
+ color = 'client',
+ value,
+ onChange,
+ ...props
+}: SearchProps) => {
+ return (
+
+
+
+ );
+};
+
+export default Search;
diff --git a/src/components/Search/styles.ts b/src/components/Search/styles.ts
new file mode 100644
index 0000000..31ef807
--- /dev/null
+++ b/src/components/Search/styles.ts
@@ -0,0 +1,170 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'gray'
+ | 'white';
+ type?: 'text' | 'email' | 'password' | 'file' | 'number';
+ fullWidth?: boolean;
+};
+
+export const Wrapper = styled.div`
+ .search {
+ width: inherit;
+ height: inherit;
+ border-radius: 5px;
+ padding: 2px;
+ color: ${({ theme }) => theme.colors.black.main};
+
+ div {
+ background: ${({ theme }) => theme.colors.white.main};
+ padding: 1rem;
+ border-radius: 5px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+ }
+
+ input {
+ width: 100%;
+ background: none;
+ border: none;
+ color: ${({ theme }) => theme.colors.black.main};
+ }
+
+ .icon {
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ }
+
+ .icon.left {
+ margin-right: 0.5rem;
+ }
+
+ ${({ color, theme }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ .search {
+ background: ${theme.colors.client.light};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.client.main};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ .search {
+ background: ${theme.colors.productOwner.light};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.productOwner.main};
+ }
+ `;
+ case 'developer':
+ return css`
+ .search {
+ background: ${theme.colors.developer.light};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.developer.main};
+ }
+ `;
+ case 'admin':
+ return css`
+ .search {
+ background: ${theme.colors.admin.light};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.admin.main};
+ }
+ `;
+ case 'success':
+ return css`
+ .search {
+ background: ${theme.colors.success.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.success.main};
+ }
+ `;
+ case 'warning':
+ return css`
+ .search {
+ background: ${theme.colors.warning.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.warning.main};
+ }
+ `;
+ case 'error':
+ return css`
+ .search {
+ background: ${theme.colors.error.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.error.main};
+ }
+ `;
+ case 'black':
+ return css`
+ .search {
+ background: ${theme.colors.black.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.black.main};
+ }
+ `;
+ case 'white':
+ return css`
+ .search {
+ background: ${theme.colors.white.main};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.white.main};
+ }
+ `;
+ default:
+ return css`
+ .search {
+ background: ${theme.colors.client.light};
+ }
+
+ .icon svg path {
+ stroke: ${theme.colors.client.main};
+ }
+ `;
+ }
+ }}
+
+ ${({ fullWidth }) =>
+ fullWidth &&
+ css`
+ width: 100%;
+ font-size: 1.25rem;
+
+ .icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+ }
+ `};
+`;
diff --git a/src/components/SectionSelector/index.tsx b/src/components/SectionSelector/index.tsx
new file mode 100644
index 0000000..ba7c21e
--- /dev/null
+++ b/src/components/SectionSelector/index.tsx
@@ -0,0 +1,34 @@
+import { Wrapper } from './styles';
+
+type SectionSelectorProps = {
+ icon: React.FunctionComponentElement>;
+ text: string;
+ color: 'client' | 'productOwner' | 'developer' | 'admin';
+ selected?: boolean;
+ disabled?: boolean;
+ onClick?: () => void;
+};
+
+const SectionSelector = ({
+ icon,
+ text,
+ color,
+ selected = false,
+ disabled = false,
+ onClick,
+}: SectionSelectorProps) => {
+ return (
+
+ {icon && {icon}}
+ {text}
+
+ );
+};
+
+export default SectionSelector;
diff --git a/src/components/SectionSelector/styles.ts b/src/components/SectionSelector/styles.ts
new file mode 100644
index 0000000..fb59760
--- /dev/null
+++ b/src/components/SectionSelector/styles.ts
@@ -0,0 +1,119 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ icon: React.FunctionComponentElement>;
+ color: 'client' | 'productOwner' | 'developer' | 'admin';
+ selected: boolean;
+ disabled: boolean;
+};
+
+export const Wrapper = styled.div`
+ width: 100%;
+ height: auto;
+ max-height: 50px;
+ padding: 15px 20px;
+ border-radius: 10px;
+ user-select: none;
+ cursor: pointer;
+
+ ${({ icon }) => {
+ if (icon)
+ return css`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ `;
+ return '';
+ }}
+
+ .icon svg {
+ display: flex;
+ align-items: center;
+ }
+
+ .icon.left {
+ margin-right: 0.5rem;
+ }
+
+ ${({ color, theme, selected }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ color: ${selected
+ ? theme.colors.client.main
+ : theme.colors.black.main};
+ background: ${selected ? theme.colors.client.light : 'none'};
+
+ svg path {
+ stroke: ${selected
+ ? theme.colors.client.main
+ : theme.colors.black.main};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ color: ${selected
+ ? theme.colors.productOwner.main
+ : theme.colors.black.main};
+ background: ${selected ? theme.colors.productOwner.light : 'none'};
+
+ svg path {
+ stroke: ${selected
+ ? theme.colors.productOwner.main
+ : theme.colors.black.main};
+ }
+ `;
+ case 'developer':
+ return css`
+ color: ${selected
+ ? theme.colors.developer.main
+ : theme.colors.black.main};
+ background: ${selected ? theme.colors.developer.light : 'none'};
+
+ svg path {
+ stroke: ${selected
+ ? theme.colors.developer.main
+ : theme.colors.black.main};
+ }
+ `;
+ case 'admin':
+ return css`
+ color: ${selected
+ ? theme.colors.admin.main
+ : theme.colors.black.main};
+ background: ${selected ? theme.colors.admin.light : 'none'};
+
+ svg path {
+ stroke: ${selected
+ ? theme.colors.admin.main
+ : theme.colors.black.main};
+ }
+ `;
+ default:
+ return css`
+ color: ${selected
+ ? theme.colors.client.main
+ : theme.colors.black.main};
+ background: ${selected ? theme.colors.client.light : 'none'};
+
+ svg path {
+ stroke: ${selected
+ ? theme.colors.client.main
+ : theme.colors.black.main};
+ }
+ `;
+ }
+ }}
+
+ ${({ disabled, theme }) =>
+ disabled &&
+ css`
+ cursor: default;
+ color: ${theme.colors.gray.main};
+ background: none;
+
+ svg path {
+ stroke: ${theme.colors.gray.main};
+ }
+ `};
+`;
diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx
new file mode 100644
index 0000000..9fbd966
--- /dev/null
+++ b/src/components/Select/index.tsx
@@ -0,0 +1,75 @@
+import { Wrapper } from './styles';
+import { Text } from '..';
+
+type SelectProps = {
+ className?: string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ options: Array<{ value: any; label: string }>;
+ value: string;
+ select?: any;
+ name: string;
+ label?: string;
+ fullWidth?: boolean;
+ onChange: (event: React.ChangeEvent) => void;
+ onBlur?: (event: React.FocusEvent) => void;
+};
+
+const Select = ({
+ color = 'client',
+ label,
+ name,
+ value,
+ select = null,
+ options,
+ onChange,
+ onBlur,
+ error,
+ errorMessage,
+ ...props
+}: SelectProps) => {
+ return (
+
+
+ {label && (
+
+ {label}
+
+ )}
+ {error && errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+
+
+
+
+
+ );
+};
+
+export default Select;
diff --git a/src/components/Select/styles.ts b/src/components/Select/styles.ts
new file mode 100644
index 0000000..78f0aa5
--- /dev/null
+++ b/src/components/Select/styles.ts
@@ -0,0 +1,154 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'gray'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ type?: 'text' | 'email' | 'password' | 'file' | 'number';
+ label?: string;
+ fullWidth?: boolean;
+};
+
+export const Wrapper = styled.div`
+ .select {
+ width: inherit;
+ height: inherit;
+ border-radius: 5px;
+ padding: 2px;
+ color: ${({ theme }) => theme.colors.black.main};
+
+ div {
+ background: ${({ theme }) => theme.colors.white.main};
+ padding: 1rem;
+ border-radius: 5px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ }
+ }
+
+ .info {
+ margin-bottom: 5px;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+
+ p {
+ background: ${({ theme }) => theme.colors.gray.dark};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .label {
+ justify-self: flex-start;
+ }
+
+ .error-message {
+ justify-self: flex-end;
+ }
+ }
+
+ select {
+ width: 100%;
+ background: none;
+ border: none;
+ color: ${({ theme }) => theme.colors.black.main};
+ background-image: url('../../assets/icons/chevron-down.svg');
+ }
+
+ ${({ color, theme }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ .select {
+ background: ${theme.colors.client.light};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ .select {
+ background: ${theme.colors.productOwner.light};
+ }
+ `;
+ case 'developer':
+ return css`
+ .select {
+ background: ${theme.colors.developer.light};
+ }
+ `;
+ case 'admin':
+ return css`
+ .select {
+ background: ${theme.colors.admin.light};
+ }
+ `;
+ case 'success':
+ return css`
+ .select {
+ background: ${theme.colors.success.main};
+ }
+ `;
+ case 'warning':
+ return css`
+ .select {
+ background: ${theme.colors.warning.main};
+ }
+ `;
+ case 'error':
+ return css`
+ .select {
+ background: ${theme.colors.error.main};
+ }
+ `;
+ case 'black':
+ return css`
+ .select {
+ background: ${theme.colors.black.main};
+ }
+ `;
+ case 'white':
+ return css`
+ .select {
+ background: ${theme.colors.white.main};
+ }
+ `;
+ default:
+ return css`
+ .select {
+ background: ${theme.colors.client.light};
+ }
+ `;
+ }
+ }}
+
+ ${({ error, theme }) =>
+ error &&
+ css`
+ .info p {
+ background: ${theme.colors.error.main};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .select {
+ background: ${theme.colors.error.main};
+ }
+ `}
+
+ ${({ fullWidth }) =>
+ fullWidth &&
+ css`
+ width: 100%;
+ font-size: 1.25rem;
+ `};
+`;
diff --git a/src/components/Sidebar/index.tsx b/src/components/Sidebar/index.tsx
new file mode 100644
index 0000000..db07a7a
--- /dev/null
+++ b/src/components/Sidebar/index.tsx
@@ -0,0 +1,286 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useLocation } from 'react-router';
+import { useLazyQuery, useReactiveVar } from '@apollo/client';
+import { roleVar, userVar } from '../../graphql/state';
+import {
+ Box,
+ ContextMenu,
+ IconButton,
+ SupportSidebar,
+ SidebarItem,
+} from '..';
+import { Add, Messaging } from '../../assets';
+import { Wrapper } from './styles';
+import {
+ CategoryOutput,
+ FeatureOutput,
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables,
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables,
+ GetAllProjectsByClientIdQuery,
+ GetAllProjectsByClientIdQueryVariables,
+ GetAllProjectsQuery,
+ GetAllProjectsQueryVariables,
+ GetAllTemplatesQuery,
+ GetAllTemplatesQueryVariables,
+ ProjectOutput,
+ TemplateOutput,
+} from '../../graphql/types';
+import { GET_ALL_CATEGORIES } from '../../graphql/category.api';
+import {
+ GET_ALL_PROJECTS,
+ GET_ALL_PROJECTS_BY_CLIENT_ID,
+} from '../../graphql/project.api';
+import { GET_ALL_TEMPLATES } from '../../graphql/template.api';
+import { GET_ALL_FEATURES } from '../../graphql/feature.api';
+
+const Sidebar = () => {
+ const role = useReactiveVar(roleVar);
+ const currentUser = useReactiveVar(userVar);
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [projects, setProjects] = useState>();
+ const [templates, setTemplates] = useState>();
+ const [features, setFeatures] = useState>();
+ const [categories, setCategories] = useState>();
+ const [supportSideBarOpen, setSupportSideBarOpen] =
+ useState(false);
+
+ const [getProjectsByClientId] = useLazyQuery<
+ GetAllProjectsByClientIdQuery,
+ GetAllProjectsByClientIdQueryVariables
+ >(GET_ALL_PROJECTS_BY_CLIENT_ID, {
+ variables: {
+ id: currentUser?.id!,
+ },
+ onCompleted({ getAllProjectsByClientId }) {
+ setProjects(getAllProjectsByClientId);
+ },
+ });
+
+ const [getProjects] = useLazyQuery<
+ GetAllProjectsQuery,
+ GetAllProjectsQueryVariables
+ >(GET_ALL_PROJECTS, {
+ onCompleted({ getAllProjects }) {
+ setProjects(getAllProjects);
+ },
+ });
+
+ const [getTemplates] = useLazyQuery<
+ GetAllTemplatesQuery,
+ GetAllTemplatesQueryVariables
+ >(GET_ALL_TEMPLATES, {
+ onCompleted({ getAllTemplates }) {
+ setTemplates(getAllTemplates);
+ },
+ });
+
+ const [getFeatures] = useLazyQuery<
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables
+ >(GET_ALL_FEATURES, {
+ onCompleted({ getAllFeatures }) {
+ setFeatures(getAllFeatures);
+ },
+ });
+
+ const [getCategories] = useLazyQuery<
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables
+ >(GET_ALL_CATEGORIES, {
+ onCompleted({ getAllCategories }) {
+ setCategories(getAllCategories);
+ },
+ });
+
+ useEffect(() => {
+ if (/project/i.test(location.pathname)) {
+ if (role !== 'client') getProjects();
+ else getProjectsByClientId({ variables: { id: currentUser?.id! } });
+ }
+
+ if (/template/i.test(location.pathname)) {
+ getTemplates();
+ }
+
+ if (/feature/i.test(location.pathname)) {
+ getFeatures();
+ }
+ if (/category/i.test(location.pathname)) {
+ getCategories();
+ }
+
+ return () => {
+ setProjects([]);
+ setTemplates([]);
+ setFeatures([]);
+ setCategories([]);
+ };
+ }, [location.pathname]);
+
+ const showAddButton = (role: string, pathname: string) => {
+ switch (role) {
+ case 'client':
+ return /project/i.test(pathname);
+ case 'productOwner':
+ return /template/i.test(pathname);
+ case 'developer':
+ return /feature/i.test(pathname) || /category/i.test(pathname);
+ }
+
+ return false;
+ };
+
+ return (
+
+ {role !== 'admin' && (
+ <>
+
+ {projects &&
+ new RegExp(/project/, 'i').test(location.pathname) &&
+ projects.map((project, index) => (
+
+
+ navigate(`/project/${project.id}`)}
+ />
+
+
+
+ ))}
+ {templates &&
+ new RegExp(/template/, 'i').test(location.pathname) &&
+ templates.map((template, index) => (
+
+
+ navigate(`/template/${template.id}`)}
+ />
+
+
+
+ ))}
+ {features &&
+ new RegExp(/feature/, 'i').test(location.pathname) &&
+ features.map((feature, index) => (
+
+
+ navigate(`/feature/${feature.id}`)}
+ />
+
+
+
+ ))}
+ {categories &&
+ new RegExp(/category/, 'i').test(location.pathname) &&
+ categories.map((category, index) => (
+
+
+ navigate(`/category/${category.id}`)}
+ />
+
+
+
+ ))}
+
+
+ {showAddButton(role as string, location.pathname) && (
+
+ }
+ color={role}
+ onClick={() => {
+ switch (role) {
+ case 'client':
+ default: {
+ if (/project/i.test(location.pathname)) {
+ navigate('/add-project');
+ }
+ break;
+ }
+ case 'productOwner': {
+ if (/project/i.test(location.pathname)) {
+ navigate('/add-project');
+ }
+ if (/template/i.test(location.pathname)) {
+ navigate('/add-template');
+ }
+ break;
+ }
+ case 'developer': {
+ if (/feature/i.test(location.pathname)) {
+ navigate('/add-feature');
+ }
+ if (/category/i.test(location.pathname)) {
+ navigate('/add-category');
+ }
+ break;
+ }
+ }
+ }}
+ />
+
+ )}
+ {/project/i.test(location.pathname) &&
+ ['client', 'productOwner'].includes(role as string) && (
+
+ }
+ color={role}
+ onClick={() =>
+ setSupportSideBarOpen(!supportSideBarOpen)
+ }
+ />
+
+ )}
+
+ >
+ )}
+ {supportSideBarOpen && (
+ setSupportSideBarOpen(false)} />
+ )}
+
+ );
+};
+
+export default Sidebar;
diff --git a/src/components/Sidebar/styles.ts b/src/components/Sidebar/styles.ts
new file mode 100644
index 0000000..e4ac6e8
--- /dev/null
+++ b/src/components/Sidebar/styles.ts
@@ -0,0 +1,25 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+export const Wrapper = styled.div`
+ position: fixed;
+ top: 0;
+ left: 0;
+ z-index: 100;
+ width: 75px;
+ height: 100%;
+ background: ${({ theme, color }) =>
+ color ? theme.colors[color].light : theme.colors.client.light};
+ display: grid;
+ grid-template-rows: 1fr auto;
+ justify-content: center;
+ padding: 55px 0px;
+ overflow-y: scroll;
+
+ &::-webkit-scrollbar {
+ width: 1px;
+ }
+`;
diff --git a/src/components/SidebarItem/index.tsx b/src/components/SidebarItem/index.tsx
new file mode 100644
index 0000000..57fafa9
--- /dev/null
+++ b/src/components/SidebarItem/index.tsx
@@ -0,0 +1,25 @@
+import { Wrapper } from './styles';
+
+type SidebarItemProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ size?: 'small' | 'medium' | 'big';
+ selected?: boolean;
+ text: string;
+ onClick: () => void;
+};
+
+const SidebarItem = ({
+ color,
+ size = 'medium',
+ selected = false,
+ text,
+ onClick,
+}: SidebarItemProps) => {
+ return (
+
+ {text}
+
+ );
+};
+
+export default SidebarItem;
diff --git a/src/components/SidebarItem/styles.ts b/src/components/SidebarItem/styles.ts
new file mode 100644
index 0000000..03b7233
--- /dev/null
+++ b/src/components/SidebarItem/styles.ts
@@ -0,0 +1,53 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+ size?: 'small' | 'medium' | 'big';
+ selected?: boolean;
+};
+
+export const Wrapper = styled.button`
+ cursor: pointer;
+ outline: none;
+ border: none;
+ border-radius: 50%;
+ background: none;
+ font-weight: bold;
+ background: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ color: ${({ theme }) => theme.colors.white.main};
+ display: grid;
+ justify-content: center;
+ align-items: center;
+
+ ${({ selected, theme }) =>
+ selected &&
+ css`
+ border: 2px solid ${theme.colors.white.main};
+ `}
+
+ ${({ size }) => {
+ switch (size) {
+ case 'small':
+ return css`
+ width: 25px;
+ height: 25px;
+ `;
+ case 'medium':
+ return css`
+ width: 35px;
+ height: 35px;
+ `;
+ case 'big':
+ return css`
+ width: 50px;
+ height: 50px;
+ `;
+ default:
+ return css`
+ width: 25px;
+ height: 25px;
+ `;
+ }
+ }}
+`;
diff --git a/src/components/Specification/index.tsx b/src/components/Specification/index.tsx
new file mode 100644
index 0000000..e289489
--- /dev/null
+++ b/src/components/Specification/index.tsx
@@ -0,0 +1,204 @@
+import { forwardRef } from 'react';
+import { Box, Text } from '..';
+import { FeatureOutput, SpecificationOutput } from '../../graphql/types';
+import { Wrapper } from './styles';
+
+type SpecificationProps = {
+ specification: SpecificationOutput;
+ features: Array;
+};
+
+const Specification = forwardRef(
+ ({ specification, features }, ref) => {
+ return (
+
+
+ Customer Requirements Specifications
+
+
+
+
+ 1. Introduction
+
+
+
+
+ 1.1. Purpose
+
+ {specification.introduction.purpose}
+
+
+
+ 1.2. Document Conventions
+
+
+ {specification.introduction.documentConventions}
+
+
+
+
+ 1.3. Intended Audience and Reading Suggestions
+
+
+ {specification.introduction.intendedAudience}
+
+
+
+
+ 1.4. Project Scope
+
+
+ {specification.introduction.projectScope}
+
+
+
+
+
+
+ 2. Overall Description
+
+
+
+
+ 2.1. Project Perspective
+
+
+ {specification.overallDescription.perspective}
+
+
+
+
+ 2.2. User Classes and Characteristics
+
+
+ {specification.overallDescription.userCharacteristics}
+
+
+
+
+ 2.3. Operating Environment
+
+
+ {specification.overallDescription.operatingEnvironment}
+
+
+
+
+ 2.4. Design and Implementation Constraints
+
+
+ {specification.overallDescription.designImplementationConstraints}
+
+
+
+
+ 2.5. User Documentation
+
+
+ {specification.overallDescription.userDocumentation}
+
+
+
+
+ 2.6. Assumptions and Dependencies
+
+
+ {specification.overallDescription.assemptionsDependencies}
+
+
+
+
+
+
+ 3. System Features
+
+
+ {features.map((feature, index) => (
+
+
+ 3.{index + 1}. {feature.name}
+
+ {feature.description}
+
+ ))}
+
+
+
+
+ 4. Other Non-Functional Requirements
+
+
+
+
+ 4.1. Performance Requirements
+
+
+ {specification.nonFunctionalRequirements.performanceRequirements}
+
+
+
+
+ 4.2. Safety Requirements
+
+
+ {specification.nonFunctionalRequirements.safetyRequirements}
+
+
+
+
+ 4.3. Security Requirements
+
+
+ {specification.nonFunctionalRequirements.securityRequirements}
+
+
+
+
+ 4.4. Software Quality Attributes
+
+
+ {
+ specification.nonFunctionalRequirements
+ .softwareQualityAttributes
+ }
+
+
+
+
+
+
+ 5. Other Requirements
+
+
+ {specification.otherRequirements}
+
+
+
+
+ 6. Glossary
+
+
+ {specification.glossary}
+
+
+
+
+ 6. Analysis Models
+
+
+ {specification.analysisModels}
+
+
+
+
+ 7. Issues List
+
+
+ {specification.issuesList}
+
+
+ );
+ }
+);
+
+export default Specification;
diff --git a/src/components/Specification/styles.ts b/src/components/Specification/styles.ts
new file mode 100644
index 0000000..08b6808
--- /dev/null
+++ b/src/components/Specification/styles.ts
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+ padding: 1rem;
+`;
diff --git a/src/components/Spinner/index.tsx b/src/components/Spinner/index.tsx
new file mode 100644
index 0000000..d03b9b5
--- /dev/null
+++ b/src/components/Spinner/index.tsx
@@ -0,0 +1,23 @@
+import { Wrapper } from './styles';
+
+type SpinnerProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'white'
+ | 'black'
+ | 'gray';
+ fullScreen?: boolean;
+};
+
+const Spinner = ({ fullScreen = false, color = 'client' }: SpinnerProps) => {
+ return (
+
+
+
+ );
+};
+
+export default Spinner;
diff --git a/src/components/Spinner/styles.ts b/src/components/Spinner/styles.ts
new file mode 100644
index 0000000..75b510e
--- /dev/null
+++ b/src/components/Spinner/styles.ts
@@ -0,0 +1,57 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'white'
+ | 'black'
+ | 'gray';
+ fullScreen?: boolean;
+};
+
+export const Wrapper = styled.div`
+ ${({ fullScreen }) =>
+ fullScreen &&
+ css`
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 99;
+ `}
+
+ .lds-dual-ring {
+ display: inline-block;
+ width: 80px;
+ height: 80px;
+ }
+ .lds-dual-ring:after {
+ content: ' ';
+ display: block;
+ width: 35px;
+ height: 35px;
+ margin: 8px;
+ border-radius: 50%;
+ border: 6px solid
+ ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ border-color: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main}
+ transparent
+ ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main}
+ transparent;
+ animation: lds-dual-ring 1.2s linear infinite;
+ }
+ @keyframes lds-dual-ring {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+ }
+`;
diff --git a/src/components/SupportSidebar/index.tsx b/src/components/SupportSidebar/index.tsx
new file mode 100644
index 0000000..81abd7a
--- /dev/null
+++ b/src/components/SupportSidebar/index.tsx
@@ -0,0 +1,113 @@
+import { useEffect, useState } from 'react';
+import { useNavigate, useLocation } from 'react-router';
+import { useReactiveVar } from '@apollo/client';
+import { roleVar } from '../../graphql/state';
+import { Box, Button, Text } from '..';
+import { Wrapper } from './styles';
+import {
+ GetProjectThreadsQuery,
+ GetProjectThreadsQueryVariables,
+ Support,
+} from '../../graphql/types.support';
+import { GET_PROJECT_THREADS } from '../../graphql/chat.api.support';
+import { Add, Empty } from '../../assets';
+import { clientSupport } from '../..';
+
+type SupportSideBarProps = {
+ onClose: () => void;
+};
+
+const SupportSidebar = ({ onClose }: SupportSideBarProps) => {
+ const role = useReactiveVar(roleVar);
+ const location = useLocation();
+ const navigate = useNavigate();
+ const [projectThreads, setProjectThreads] = useState>();
+
+ useEffect(() => {
+ (async () => {
+ if (/\/project/i.test(location.pathname)) {
+ const threads = await clientSupport.query<
+ GetProjectThreadsQuery,
+ GetProjectThreadsQueryVariables
+ >({
+ query: GET_PROJECT_THREADS,
+ variables: {
+ projectId: location.pathname.split('/')[2] as string,
+ },
+ fetchPolicy: 'network-only',
+ });
+ setProjectThreads(threads?.data?.threads!);
+ }
+ })();
+ }, [location.pathname]);
+
+ return (
+
+
+
+
+
+
+ Support
+
+
+ }
+ onClick={() => {
+ onClose();
+ navigate(`/support/${location.pathname.split('/')[2]}`);
+ }}
+ />
+
+ {projectThreads && projectThreads.length > 0 ? (
+
+ {projectThreads.map((thread) => (
+ {
+ onClose();
+ navigate(
+ `/support/${location.pathname.split('/')[2]}/${thread.id}`
+ );
+ }}
+ >
+ {thread.title}
+
+ ))}
+
+ ) : (
+
+
+
+
+
+ )}
+
+
+ );
+};
+
+export default SupportSidebar;
diff --git a/src/components/SupportSidebar/styles.ts b/src/components/SupportSidebar/styles.ts
new file mode 100644
index 0000000..3b5aec6
--- /dev/null
+++ b/src/components/SupportSidebar/styles.ts
@@ -0,0 +1,34 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+export const Wrapper = styled.div`
+ position: fixed;
+ top: 0;
+ left: 75px;
+ z-index: 100;
+ width: 500px;
+ height: 100vh;
+ background: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+
+ .overlay {
+ position: fixed;
+ top: 0;
+ left: 575px;
+ width: 100vw;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.3);
+ }
+
+ .empty {
+ fill: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ }
+
+ .messaging-empty {
+ fill: white;
+ }
+`;
diff --git a/src/components/TemplateCard/index.tsx b/src/components/TemplateCard/index.tsx
new file mode 100644
index 0000000..6a19bf6
--- /dev/null
+++ b/src/components/TemplateCard/index.tsx
@@ -0,0 +1,50 @@
+import { Box, Text } from '..';
+import { TemplateOutput } from '../../graphql/types';
+import { theme } from '../../themes';
+
+type TemplateCardProps = {
+ template: TemplateOutput;
+ selectable?: boolean;
+ selected?: boolean;
+ toggleSelect?: () => void;
+ color: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+const TemplateCard = ({
+ template,
+ selectable = false,
+ selected = false,
+ toggleSelect = () => {},
+ color,
+}: TemplateCardProps) => {
+ return (
+ {}}
+ display='grid'
+ gridTemplateRows='auto'
+ alignItems='center'
+ rowGap='10px'
+ borderRadius='10px'
+ cursor='pointer'
+ >
+
+
+
+ {template.name}
+
+
+
+
+
+ {template.description}
+
+
+
+ );
+};
+
+export default TemplateCard;
diff --git a/src/components/Text/index.tsx b/src/components/Text/index.tsx
new file mode 100644
index 0000000..a7547cb
--- /dev/null
+++ b/src/components/Text/index.tsx
@@ -0,0 +1,38 @@
+import { Wrapper } from './styles';
+
+type TextProps = {
+ children?: React.ReactNode | JSX.Element | string;
+ className?: string;
+ variant?: 'display' | 'headline' | 'title' | 'subheader' | 'body' | 'caption';
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white'
+ | string;
+ align?: 'inherit' | 'left' | 'center' | 'right' | 'justify';
+ display?: 'initial' | 'block' | 'inline';
+ gutterBottom?: boolean;
+ lineThrough?: boolean;
+ weight?: 'initial' | 'normal' | 'bold' | number;
+};
+
+const Text = ({
+ children,
+ variant = 'body',
+ className,
+ ...props
+}: TextProps) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Text;
diff --git a/src/components/Text/styles.ts b/src/components/Text/styles.ts
new file mode 100644
index 0000000..2fe7f2e
--- /dev/null
+++ b/src/components/Text/styles.ts
@@ -0,0 +1,140 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white'
+ | string;
+ align?: 'inherit' | 'left' | 'center' | 'right' | 'justify';
+ display?: 'initial' | 'block' | 'inline';
+ gutterBottom?: boolean;
+ lineThrough?: boolean;
+ weight?: 'initial' | 'normal' | 'bold' | number;
+};
+
+export const Wrapper = styled.p`
+ ${({ color, theme }) => {
+ if (!color)
+ return css`
+ color: inherit;
+ `;
+ switch (color) {
+ case 'client':
+ return css`
+ color: ${theme.colors.client.main};
+ `;
+ case 'productOwner':
+ return css`
+ color: ${theme.colors.productOwner.main};
+ `;
+ case 'developer':
+ return css`
+ color: ${theme.colors.developer.main};
+ `;
+ case 'admin':
+ return css`
+ color: ${theme.colors.admin.main};
+ `;
+ case 'success':
+ return css`
+ color: ${theme.colors.success.main};
+ `;
+ case 'warning':
+ return css`
+ color: ${theme.colors.warning.main};
+ `;
+ case 'error':
+ return css`
+ color: ${theme.colors.error.main};
+ `;
+ case 'black':
+ return css`
+ color: ${theme.colors.black.main};
+ `;
+ case 'white':
+ return css`
+ color: ${theme.colors.white.main};
+ `;
+ default:
+ return css`
+ color: ${color};
+ `;
+ }
+ }}
+
+ ${({ display }) =>
+ display
+ ? css`
+ display: ${display};
+ `
+ : css`
+ display: block;
+ `}
+
+ ${({ gutterBottom }) =>
+ gutterBottom &&
+ css`
+ margin-bottom: 0.35rem;
+ `};
+
+ ${({ lineThrough }) =>
+ lineThrough &&
+ css`
+ text-decoration: line-through;
+ `};
+
+ ${({ align }) =>
+ align
+ ? css`
+ align: ${align};
+ `
+ : css`
+ align: initial;
+ `}
+
+ ${({ weight }) =>
+ weight
+ ? css`
+ font-weight: ${weight};
+ `
+ : css`
+ font-weight: initial;
+ `}
+
+ &.display {
+ font-size: 2.25rem;
+ line-height: 3rem;
+ }
+
+ &.headline {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ }
+
+ &.title {
+ font-size: 1.25rem;
+ line-height: 1.75rem;
+ }
+
+ &.subheader {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ }
+
+ &.body {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ }
+
+ &.caption {
+ font-size: 0.75rem;
+ line-height: 1rem;
+ }
+`;
diff --git a/src/components/TextArea/index.tsx b/src/components/TextArea/index.tsx
new file mode 100644
index 0000000..7112d28
--- /dev/null
+++ b/src/components/TextArea/index.tsx
@@ -0,0 +1,69 @@
+import { Wrapper } from './styles';
+import { Text } from '..';
+
+type TextAreaProps = {
+ className?: string;
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ value: string;
+ label?: string;
+ name: string;
+ placeholder?: string;
+ fullWidth?: boolean;
+ onChange: (event: React.ChangeEvent) => void;
+ onBlur?: (event: React.FocusEvent) => void;
+};
+
+const TextArea = ({
+ color = 'client',
+ label,
+ name,
+ placeholder,
+ value,
+ onChange,
+ onBlur,
+ error,
+ errorMessage,
+ ...props
+}: TextAreaProps) => {
+ return (
+
+
+ {label && (
+
+ {label}
+
+ )}
+ {error && errorMessage && (
+
+ {errorMessage}
+
+ )}
+
+
+
+ );
+};
+
+export default TextArea;
diff --git a/src/components/TextArea/styles.ts b/src/components/TextArea/styles.ts
new file mode 100644
index 0000000..23a4b72
--- /dev/null
+++ b/src/components/TextArea/styles.ts
@@ -0,0 +1,150 @@
+import styled, { css } from 'styled-components';
+
+type WrapperProps = {
+ color?:
+ | 'client'
+ | 'productOwner'
+ | 'developer'
+ | 'admin'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'black'
+ | 'gray'
+ | 'white';
+ error?: boolean;
+ errorMessage?: string;
+ label?: string;
+ fullWidth?: boolean;
+};
+
+export const Wrapper = styled.div`
+ .textarea {
+ width: inherit;
+ height: inherit;
+ border-radius: 5px;
+ padding: 2px;
+ color: ${({ theme }) => theme.colors.black.main};
+
+ div {
+ background: ${({ theme }) => theme.colors.white.main};
+ padding: 1rem;
+ border-radius: 5px;
+ }
+ }
+
+ .info {
+ margin-bottom: 5px;
+ display: grid;
+ grid-template-columns: 1fr auto;
+ align-items: center;
+
+ p {
+ background: ${({ theme }) => theme.colors.gray.dark};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .label {
+ justify-self: flex-start;
+ }
+
+ .error-message {
+ justify-self: flex-end;
+ }
+ }
+
+ textarea {
+ width: 100%;
+ background: none;
+ border: none;
+ resize: none;
+ color: ${({ theme }) => theme.colors.black.main};
+ }
+
+ ${({ color, theme }) => {
+ switch (color) {
+ case 'client':
+ return css`
+ .textarea {
+ background: ${theme.colors.client.light};
+ }
+ `;
+ case 'productOwner':
+ return css`
+ .textarea {
+ background: ${theme.colors.productOwner.light};
+ }
+ `;
+ case 'developer':
+ return css`
+ .textarea {
+ background: ${theme.colors.developer.light};
+ }
+ `;
+ case 'admin':
+ return css`
+ .textarea {
+ background: ${theme.colors.admin.light};
+ }
+ `;
+ case 'success':
+ return css`
+ .textarea {
+ background: ${theme.colors.success.main};
+ }
+ `;
+ case 'warning':
+ return css`
+ .textarea {
+ background: ${theme.colors.warning.main};
+ }
+ `;
+ case 'error':
+ return css`
+ .textarea {
+ background: ${theme.colors.error.main};
+ }
+ `;
+ case 'black':
+ return css`
+ .textarea {
+ background: ${theme.colors.black.main};
+ }
+ `;
+ case 'white':
+ return css`
+ .textarea {
+ background: ${theme.colors.white.main};
+ }
+ `;
+ default:
+ return css`
+ .textarea {
+ background: ${theme.colors.client.light};
+ }
+ `;
+ }
+ }}
+
+ ${({ error, theme }) =>
+ error &&
+ css`
+ .info p {
+ background: ${theme.colors.error.main};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+
+ .textarea {
+ background: ${theme.colors.error.main};
+ }
+ `}
+
+ ${({ fullWidth }) =>
+ fullWidth &&
+ css`
+ width: 100%;
+ font-size: 1.25rem;
+ `};
+`;
diff --git a/src/components/index.tsx b/src/components/index.tsx
new file mode 100644
index 0000000..ed61ee0
--- /dev/null
+++ b/src/components/index.tsx
@@ -0,0 +1,65 @@
+import Button from './Button';
+import IconButton from './IconButton';
+import Box from './Box';
+import Text from './Text';
+import Link from './Link';
+import Input from './Input';
+import TextArea from './TextArea';
+import Select from './Select';
+import Search from './Search';
+import Avatar from './Avatar';
+import ContextMenu from './ContextMenu';
+import Spinner from './Spinner';
+import Alert from './Alert';
+import CheckBox from './CheckBox';
+import Menu from './Menu';
+import Navbar from './Navbar';
+import Sidebar from './Sidebar';
+import Protected from './Protected';
+import Public from './Public';
+import SectionSelector from './SectionSelector';
+import Modal from './Modal';
+import SidebarItem from './SidebarItem';
+import ImagePreview from './ImagePreview';
+import FeatureCard from './FeatureCard';
+import FrontendFeatureCard from './FrontendFeatureCard';
+import BackendFeatureCard from './BackendFeatureCard';
+import Specification from './Specification';
+import Chip from './Chip';
+import CategoryCard from './CategoryCard';
+import TemplateCard from './TemplateCard';
+import SupportSidebar from './SupportSidebar';
+
+export {
+ Button,
+ IconButton,
+ Box,
+ Text,
+ Link,
+ Input,
+ TextArea,
+ Select,
+ Search,
+ Avatar,
+ ContextMenu,
+ Menu,
+ Spinner,
+ Alert,
+ CheckBox,
+ Navbar,
+ Sidebar,
+ Protected,
+ Public,
+ SectionSelector,
+ Modal,
+ SidebarItem,
+ ImagePreview,
+ FeatureCard,
+ FrontendFeatureCard,
+ BackendFeatureCard,
+ Specification,
+ Chip,
+ CategoryCard,
+ TemplateCard,
+ SupportSidebar,
+};
diff --git a/src/graphql/admin.api.ts b/src/graphql/admin.api.ts
new file mode 100644
index 0000000..c74b14c
--- /dev/null
+++ b/src/graphql/admin.api.ts
@@ -0,0 +1,67 @@
+import gql from 'graphql-tag';
+
+export const GET_ALL_USERS = gql`
+ query GetAllUsers {
+ getAllUsers {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const GET_USER_BY_ID = gql`
+ query GetUserById($id: String!) {
+ getUserById(id: $id) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const CREATE_USER = gql`
+ mutation CreateUser($user: UserInput!) {
+ createUser(user: $user) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
diff --git a/src/graphql/auth.api.ts b/src/graphql/auth.api.ts
new file mode 100644
index 0000000..01ccc84
--- /dev/null
+++ b/src/graphql/auth.api.ts
@@ -0,0 +1,192 @@
+import gql from 'graphql-tag';
+
+export const SIGNUP = gql`
+ mutation Signup($email: String!, $password: String!) {
+ signup(email: $email, password: $password) {
+ user {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ token
+ }
+ }
+`;
+
+export const LOGIN = gql`
+ mutation Login($email: String!, $password: String!) {
+ login(email: $email, password: $password) {
+ user {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ token
+ }
+ }
+`;
+
+export const RESET_PASSWORD = gql`
+ mutation ResetPassword($email: String!) {
+ resetUserPassword(email: $email) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const CONFIRM_USER_RESET_PASSWORD = gql`
+ mutation ConfirmUserResetPassword($id: String!, $password: String!) {
+ confirmUserResetPassword(id: $id, password: $password) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const UPDATE_USER_INFO = gql`
+ mutation UpdateUserInfo($user: UpdateUserInput!) {
+ updateUserInfo(user: $user) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const UPDATE_USER_PASSWORD = gql`
+ mutation UpdateUserPassword($id: String!, $password: PasswordInput!) {
+ updateUserPassword(id: $id, password: $password) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const DELETE_USER = gql`
+ mutation DeleteUser($id: String!, $password: String!) {
+ deleteUser(id: $id, password: $password) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const GET_USER_BY_ID = gql`
+ query GetUserById($id: String!) {
+ getUserById(id: $id) {
+ id
+ email
+ firstName
+ lastName
+ phone {
+ prefix
+ number
+ }
+ address {
+ place
+ city
+ country
+ zip
+ }
+ role
+ }
+ }
+`;
+
+export const GET_COUNTRY_CODES = gql`
+ query GetCountryCodes {
+ getCountryCode {
+ prefix
+ country
+ }
+ }
+`;
diff --git a/src/graphql/category.api.ts b/src/graphql/category.api.ts
new file mode 100644
index 0000000..c4b6862
--- /dev/null
+++ b/src/graphql/category.api.ts
@@ -0,0 +1,71 @@
+import gql from 'graphql-tag';
+
+export const GET_ALL_CATEGORIES = gql`
+ query GetAllCategories {
+ getAllCategories {
+ id
+ name
+ description
+ image {
+ name
+ src
+ }
+ }
+ }
+`;
+
+export const GET_CATEGORY_BY_ID = gql`
+ query GetCategoryById($id: String!) {
+ getCategoryById(id: $id) {
+ id
+ name
+ description
+ image {
+ name
+ src
+ }
+ }
+ }
+`;
+
+export const ADD_CATEGORY = gql`
+ mutation AddCategory($category: CategoryInput!) {
+ addCategory(category: $category) {
+ id
+ name
+ description
+ image {
+ name
+ src
+ }
+ }
+ }
+`;
+
+export const UPDATE_CATEGORY = gql`
+ mutation UpdateCategory($id: String!, $category: CategoryInput!) {
+ updateCategory(id: $id, category: $category) {
+ id
+ name
+ description
+ image {
+ name
+ src
+ }
+ }
+ }
+`;
+
+export const DELETE_CATEGORY = gql`
+ mutation DeleteCategory($id: String!) {
+ deleteCategory(id: $id) {
+ id
+ name
+ description
+ image {
+ name
+ src
+ }
+ }
+ }
+`;
diff --git a/src/graphql/chat.api.support.ts b/src/graphql/chat.api.support.ts
new file mode 100644
index 0000000..0673bd7
--- /dev/null
+++ b/src/graphql/chat.api.support.ts
@@ -0,0 +1,76 @@
+import gql from 'graphql-tag';
+
+export const GET_PROJECT_THREADS = gql`
+ query GetProjectThreads($projectId: String!) {
+ threads(projectId: $projectId) {
+ id
+ title
+ threadDescription
+ userMessages {
+ id
+ username
+ text
+ }
+ }
+ }
+`;
+
+export const GET_THREAD_BY_ID = gql`
+ query GetThreadById($threadId: String!) {
+ thread(threadId: $threadId) {
+ id
+ title
+ threadDescription
+ userMessages {
+ id
+ username
+ text
+ }
+ }
+ }
+`;
+
+export const MESSAGES = gql`
+ query Messages($threadId: String!) {
+ messages(threadId: $threadId) {
+ username
+ text
+ }
+ }
+`;
+
+export const CREATE_THREAD = gql`
+ mutation CreateThread(
+ $projectId: String!
+ $title: String!
+ $threadDescription: String!
+ ) {
+ createThread(
+ projectId: $projectId
+ title: $title
+ threadDescription: $threadDescription
+ ) {
+ id
+ }
+ }
+`;
+
+export const SEND_MSG = gql`
+ mutation SendMessage($threadId: String!, $username: String!, $text: String!) {
+ sendMessage(threadId: $threadId, username: $username, text: $text)
+ }
+`;
+
+export const MESSAGES_SUBSCRIPTION = gql`
+ subscription messagesSubscription {
+ messages {
+ mutationType
+ id
+ userMessages {
+ id
+ username
+ text
+ }
+ }
+ }
+`;
diff --git a/src/graphql/feature.api.ts b/src/graphql/feature.api.ts
new file mode 100644
index 0000000..14ade39
--- /dev/null
+++ b/src/graphql/feature.api.ts
@@ -0,0 +1,155 @@
+import gql from 'graphql-tag';
+
+export const GET_ALL_FEATURES = gql`
+ query GetAllFeatures {
+ getAllFeatures {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const GET_FEATURE_BY_ID = gql`
+ query GetFeatureById($id: String!) {
+ getFeatureById(id: $id) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const ADD_FEATURE = gql`
+ mutation AddFeature($feature: FeatureInput!) {
+ addFeature(feature: $feature) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const UPDATE_FEATURE = gql`
+ mutation UpdateFeature($id: String!, $feature: FeatureInput!) {
+ updateFeature(id: $id, feature: $feature) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const DELETE_FEATURE = gql`
+ mutation DeleteFeature($id: String!) {
+ deleteFeature(id: $id) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const ADD_FEATURE_WIREFRAMES = gql`
+ mutation AddFeatureWireframes($id: String!, $wireframes: [InputFile!]!) {
+ addFeatureWireframes(id: $id, wireframes: $wireframes) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
+
+export const DELETE_FEATURE_WIREFRAME = gql`
+ mutation DeleteFeatureWireframe($id: String!) {
+ deleteFeatureWireframe(id: $id) {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ }
+`;
diff --git a/src/graphql/project.api.ts b/src/graphql/project.api.ts
new file mode 100644
index 0000000..525a6e2
--- /dev/null
+++ b/src/graphql/project.api.ts
@@ -0,0 +1,1201 @@
+import gql from 'graphql-tag';
+
+export const GET_ALL_PROJECTS = gql`
+ query GetAllProjects {
+ getAllProjects {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const GET_ALL_PROJECTS_BY_CLIENT_ID = gql`
+ query GetAllProjectsByClientId($id: String!) {
+ getAllProjectsByClientId(id: $id) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const GET_PROJECT_BY_ID = gql`
+ query GetProjectById($id: String!) {
+ getProjectById(id: $id) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const ADD_PROJECT = gql`
+ mutation AddProject($project: ProjectInput!) {
+ addProject(project: $project) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const CHANGE_PROJECT_STATE = gql`
+ mutation ChangeProjectState($id: String!, $state: State!) {
+ changeProjectState(id: $id, state: $state) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const UPDATE_PROJECT = gql`
+ mutation UpdateProject($id: String!, $name: String!, $image: InputFile!) {
+ updateProject(id: $id, name: $name, image: $image) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const ADD_PROJECT_PROPOSAL = gql`
+ mutation AddProjectProposal($id: String!, $proposal: ProposalInput!) {
+ addProjectProposal(id: $id, proposal: $proposal) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const ADD_PROJECT_DESIGN = gql`
+ mutation AddProjectDesign($design: ProjectFileInput!) {
+ addProjectDesign(design: $design) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const ADD_PROJECT_MVP = gql`
+ mutation AddProjectMvp($mvp: ProjectFileInput!) {
+ addProjectMvp(mvp: $mvp) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
+
+export const ADD_PROJECT_FULL_BUILD = gql`
+ mutation AddProjectFullBuild($fullBuild: ProjectFullBuildInput!) {
+ addProjectFullBuild(fullBuild: $fullBuild) {
+ id
+ clientId
+ name
+ image {
+ name
+ src
+ }
+ platforms
+ template {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ state
+ proposal {
+ devtime {
+ months
+ days
+ hours
+ }
+ summary
+ purpose
+ resources {
+ resourceType
+ developers
+ }
+ }
+ paymentOption {
+ optOne
+ optTwo
+ optThree
+ }
+ delivrable {
+ specification {
+ name
+ src
+ }
+ fullBuild
+ mvp {
+ name
+ src
+ }
+ design {
+ name
+ src
+ }
+ }
+ totalPrice
+ }
+ }
+`;
diff --git a/src/graphql/prototype.api.ts b/src/graphql/prototype.api.ts
new file mode 100644
index 0000000..33dea31
--- /dev/null
+++ b/src/graphql/prototype.api.ts
@@ -0,0 +1,106 @@
+import gql from 'graphql-tag';
+
+export const GET_PROTOTYPE_BY_ID = gql`
+ query GetPrototypeById($id: String!) {
+ getPrototypeById(id: $id) {
+ id
+ template
+ prototype {
+ feature {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ connections {
+ to
+ releations {
+ back
+ forword
+ }
+ }
+ }
+ }
+ }
+`;
+
+export const ADD_PROTOTYPE = gql`
+ mutation AddPrototype($prototype: TemplateProtoTypeInput!) {
+ addPrototype(prototype: $prototype) {
+ id
+ template
+ prototype {
+ feature {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ connections {
+ to
+ releations {
+ back
+ forword
+ }
+ }
+ }
+ }
+ }
+`;
+
+export const UPDATE_PROTOTYPE = gql`
+ mutation UpdatePrototype($prototype: TemplateProtoTypeInput!) {
+ updatePrototype(prototype: $prototype) {
+ id
+ template
+ prototype {
+ feature {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ connections {
+ to
+ releations {
+ back
+ forword
+ }
+ }
+ }
+ }
+ }
+`;
diff --git a/src/graphql/state.ts b/src/graphql/state.ts
new file mode 100644
index 0000000..5c82426
--- /dev/null
+++ b/src/graphql/state.ts
@@ -0,0 +1,10 @@
+import { makeVar } from '@apollo/client';
+import { UserOutput } from './types';
+
+export const tokenVar = makeVar(undefined);
+
+export const roleVar = makeVar<
+ 'client' | 'productOwner' | 'developer' | 'admin' | undefined
+>(undefined);
+
+export const userVar = makeVar(undefined);
diff --git a/src/graphql/template.api.ts b/src/graphql/template.api.ts
new file mode 100644
index 0000000..d147905
--- /dev/null
+++ b/src/graphql/template.api.ts
@@ -0,0 +1,460 @@
+import gql from 'graphql-tag';
+
+export const GET_ALL_TEMPLATES = gql`
+ query GetAllTemplates {
+ getAllTemplates {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const GET_TEMPLATE_BY_ID = gql`
+ query GetTemplateById($id: String!) {
+ getTemplateById(id: $id) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const GET_ALL_TEMPLATES_BY_CATEGORIES_ID = gql`
+ query GetAllTemplatesByCategoriesId($categories: [String!]!) {
+ getAllTemplatesByCategoriesId(categories: $categories) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const ADD_TEMPLATE = gql`
+ mutation AddTemplate($template: TemplateInput!) {
+ addTemplate(template: $template) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const UPDATE_TEMPLATE = gql`
+ mutation UpdateTemplate(
+ $id: String!
+ $template: TemplateUpdateInput!
+ $specification: SpecificationInput
+ ) {
+ updateTemplate(
+ id: $id
+ template: $template
+ specification: $specification
+ ) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const UPDATE_TEMPLATE_FEATURES = gql`
+ mutation UpdateTemplateFeatures($id: String!, $featuresId: [String!]!) {
+ updateTemplateFeatures(id: $id, featuresId: $featuresId) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const ADD_TEMPLATE_SPECIFICATION = gql`
+ mutation AddTemplateSpecification(
+ $id: String!
+ $specification: SpecificationInput!
+ ) {
+ addTemplateSpecification(id: $id, specification: $specification) {
+ id
+ name
+ description
+ category
+ features {
+ id
+ name
+ description
+ featureType
+ image {
+ name
+ src
+ }
+ wireframes {
+ id
+ name
+ src
+ }
+ price
+ repo
+ }
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
+
+export const DELETE_TEMPLATE = gql`
+ mutation DeleteTemplate($id: String!) {
+ deleteTemplate(id: $id) {
+ id
+ name
+ description
+ category
+ features
+ image {
+ name
+ src
+ }
+ specification {
+ introduction {
+ purpose
+ documentConventions
+ intendedAudience
+ projectScope
+ }
+ overallDescription {
+ perspective
+ userCharacteristics
+ operatingEnvironment
+ designImplementationConstraints
+ userDocumentation
+ assemptionsDependencies
+ }
+ nonFunctionalRequirements {
+ performanceRequirements
+ safetyRequirements
+ securityRequirements
+ softwareQualityAttributes
+ }
+ otherRequirements
+ glossary
+ analysisModels
+ issuesList
+ }
+ }
+ }
+`;
diff --git a/src/graphql/types.support.ts b/src/graphql/types.support.ts
new file mode 100644
index 0000000..20571b7
--- /dev/null
+++ b/src/graphql/types.support.ts
@@ -0,0 +1,180 @@
+/* eslint-disable */
+export type Maybe = T | null;
+export type InputMaybe = Maybe;
+export type Exact = {
+ [K in keyof T]: T[K];
+};
+export type MakeOptional = Omit & {
+ [SubKey in K]?: Maybe;
+};
+export type MakeMaybe = Omit & {
+ [SubKey in K]: Maybe;
+};
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+ ID: string;
+ String: string;
+ Boolean: boolean;
+ Int: number;
+ Float: number;
+};
+
+export type MutationRoot = {
+ __typename?: 'MutationRoot';
+ createThread: Support;
+ deleteThread?: Maybe;
+ sendMessage: Scalars['ID'];
+};
+
+export type MutationRootCreateThreadArgs = {
+ projectId: Scalars['String'];
+ threadDescription: Scalars['String'];
+ title: Scalars['String'];
+};
+
+export type MutationRootDeleteThreadArgs = {
+ threadId: Scalars['String'];
+};
+
+export type MutationRootSendMessageArgs = {
+ text: Scalars['String'];
+ threadId: Scalars['String'];
+ username: Scalars['String'];
+};
+
+export enum MutationType {
+ Created = 'CREATED',
+}
+
+export type QueryRoot = {
+ __typename?: 'QueryRoot';
+ messages?: Maybe>;
+ thread?: Maybe;
+ threads?: Maybe>;
+};
+
+export type QueryRootMessagesArgs = {
+ threadId: Scalars['ID'];
+};
+
+export type QueryRootThreadArgs = {
+ threadId: Scalars['ID'];
+};
+
+export type QueryRootThreadsArgs = {
+ projectId: Scalars['ID'];
+};
+
+export type StreamChanged = {
+ __typename?: 'StreamChanged';
+ id: Scalars['ID'];
+ mutationType: MutationType;
+ userMessages?: Maybe;
+};
+
+export type SubscriptionRoot = {
+ __typename?: 'SubscriptionRoot';
+ interval: Scalars['Int'];
+ messages: StreamChanged;
+};
+
+export type SubscriptionRootIntervalArgs = {
+ n?: Scalars['Int'];
+};
+
+export type Support = {
+ __typename?: 'Support';
+ id: Scalars['ID'];
+ projectId: Scalars['ID'];
+ threadDescription: Scalars['String'];
+ title: Scalars['String'];
+ userMessages: Array;
+};
+
+export type UserMessages = {
+ __typename?: 'UserMessages';
+ id: Scalars['String'];
+ username: Scalars['String'];
+ text: Scalars['String'];
+};
+
+export type GetProjectThreadsQueryVariables = Exact<{
+ projectId: Scalars['String'];
+}>;
+
+export type GetProjectThreadsQuery = { __typename?: 'QueryRoot' } & {
+ threads: Array<
+ { __typename?: 'Support' } & Pick<
+ Support,
+ 'id' | 'title' | 'projectId' | 'threadDescription' | 'userMessages'
+ > & {
+ userMessages: Array<
+ { __typename?: 'UserMessage' } & Pick<
+ UserMessages,
+ 'id' | 'username' | 'text'
+ >
+ >;
+ }
+ >;
+};
+
+export type GetThreadByIdQueryVariables = Exact<{
+ threadId: Scalars['String'];
+}>;
+
+export type GetThreadByIdQuery = { __typename?: 'QueryRoot' } & {
+ thread: { __typename?: 'Support' } & Pick<
+ Support,
+ 'id' | 'title' | 'projectId' | 'threadDescription' | 'userMessages'
+ > & {
+ userMessages: Array<
+ { __typename?: 'UserMessages' } & Pick
+ >;
+ };
+};
+
+export type MessagesQueryVariables = Exact<{
+ threadId: Scalars['String'];
+}>;
+
+export type MessagesQuery = { __typename?: 'QueryRoot' } & {
+ messages: Array<
+ { __typename?: 'UserMessages' } & Pick
+ >;
+};
+
+export type CreateThreadMutationVariables = Exact<{
+ projectId: Scalars['String'];
+ title: Scalars['String'];
+ threadDescription: Scalars['String'];
+}>;
+
+export type CreateThreadMutation = { __typename?: 'MutationRoot' } & Pick<
+ MutationRoot,
+ 'createThread'
+>;
+
+export type SendMsgMutationVariables = Exact<{
+ threadId: Scalars['String'];
+ username: Scalars['String'];
+ text: Scalars['String'];
+}>;
+
+export type SendMsgMutation = { __typename?: 'MutationRoot' } & Pick<
+ MutationRoot,
+ 'sendMessage'
+>;
+
+export type MessagesSubscription = { __typename?: 'SubscriptionRoot' } & {
+ messages: { __typename?: 'StreamChanged' } & Pick<
+ StreamChanged,
+ 'mutationType'
+ > & {
+ userMessages?: Maybe<
+ { __typename?: 'UserMessages' } & Pick<
+ UserMessages,
+ 'id' | 'username' | 'text'
+ >
+>;
+ };
+};
diff --git a/src/graphql/types.ts b/src/graphql/types.ts
new file mode 100644
index 0000000..c761dc2
--- /dev/null
+++ b/src/graphql/types.ts
@@ -0,0 +1,2904 @@
+export type Maybe = T | null;
+export type Exact = {
+ [K in keyof T]: T[K];
+};
+export type MakeOptional = Omit & {
+ [SubKey in K]?: Maybe;
+};
+export type MakeMaybe = Omit & {
+ [SubKey in K]: Maybe;
+};
+/** All built-in and custom scalars, mapped to their actual values */
+export type Scalars = {
+ ID: string;
+ String: string;
+ Boolean: boolean;
+ Int: number;
+ Float: number;
+};
+
+export type AddressInput = {
+ place: Scalars['String'];
+ city: Scalars['String'];
+ zip: Scalars['String'];
+ country: Scalars['String'];
+};
+
+export type AddressOutput = {
+ __typename?: 'AddressOutput';
+ place: Scalars['String'];
+ city: Scalars['String'];
+ zip: Scalars['String'];
+ country: Scalars['String'];
+};
+
+export type CategoryInput = {
+ name: Scalars['String'];
+ description: Scalars['String'];
+ image: InputFile;
+};
+
+export type CategoryOutput = {
+ __typename?: 'CategoryOutput';
+ id: Scalars['String'];
+ name: Scalars['String'];
+ description: Scalars['String'];
+ image: File;
+};
+
+export type ConnectionsInput = {
+ to: Scalars['String'];
+ releations: RelationsInput;
+};
+
+export type ConnectionsOutput = {
+ __typename?: 'ConnectionsOutput';
+ to: Scalars['String'];
+ releations: RelationsOutput;
+};
+
+export type CountryPrefixModel = {
+ __typename?: 'CountryPrefixModel';
+ country: Scalars['String'];
+ prefix: Scalars['String'];
+};
+
+export type DelivrableInput = {
+ specification: Scalars['Boolean'];
+ fullBuild: Scalars['Boolean'];
+ mvp: Scalars['Boolean'];
+ design: Scalars['Boolean'];
+};
+
+export type DelivrableOutput = {
+ __typename?: 'DelivrableOutput';
+ specification: File;
+ fullBuild: Scalars['String'];
+ mvp: File;
+ design: File;
+};
+
+export type DevtimeInput = {
+ months: Scalars['Int'];
+ days: Scalars['Int'];
+ hours: Scalars['Int'];
+};
+
+export type DevtimeOutput = {
+ __typename?: 'DevtimeOutput';
+ months: Scalars['Int'];
+ days: Scalars['Int'];
+ hours: Scalars['Int'];
+};
+
+export type FeatureInput = {
+ name: Scalars['String'];
+ description: Scalars['String'];
+ featureType: Scalars['String'];
+ image: InputFile;
+ wireframes?: Maybe>;
+ price: Scalars['Float'];
+ repo: Scalars['String'];
+};
+
+export type FeatureOutput = {
+ __typename?: 'FeatureOutput';
+ id: Scalars['String'];
+ name: Scalars['String'];
+ description: Scalars['String'];
+ featureType: Scalars['String'];
+ image: File;
+ wireframes?: Maybe>;
+ price: Scalars['Float'];
+ repo: Scalars['String'];
+};
+
+export type File = {
+ __typename?: 'File';
+ name: Scalars['String'];
+ src: Scalars['String'];
+};
+
+export type FileWithOutOId = {
+ __typename?: 'FileWithOutOId';
+ id: Scalars['String'];
+ name: Scalars['String'];
+ src: Scalars['String'];
+};
+
+export type InputFile = {
+ name: Scalars['String'];
+ src: Scalars['String'];
+};
+
+export type IntroductionInput = {
+ purpose: Scalars['String'];
+ documentConventions: Scalars['String'];
+ intendedAudience: Scalars['String'];
+ projectScope: Scalars['String'];
+};
+
+export type IntroductionOutput = {
+ __typename?: 'IntroductionOutput';
+ purpose: Scalars['String'];
+ documentConventions: Scalars['String'];
+ intendedAudience: Scalars['String'];
+ projectScope: Scalars['String'];
+};
+
+export type MutationRoot = {
+ __typename?: 'MutationRoot';
+ signup: UserAuthenticationOutput;
+ createUser: UserOutput;
+ login: UserAuthenticationOutput;
+ deleteUser: UserOutput;
+ updateUserInfo: UserOutput;
+ updateUserPassword: UserOutput;
+ resetUserPassword: UserOutput;
+ confirmUserResetPassword: UserOutput;
+ deleteCategory: CategoryOutput;
+ deleteFeature: FeatureOutput;
+ deleteTemplate: TemplateDefactoredOutput;
+ updateTemplateFeatures: TemplateOutput;
+ addTemplateSpecification: TemplateOutput;
+ addCategory: CategoryOutput;
+ updateCategory: CategoryOutput;
+ addFeature: FeatureOutput;
+ updateFeature: FeatureOutput;
+ deleteFeatureWireframe: FeatureOutput;
+ addFeatureWireframes: FeatureOutput;
+ addTemplate: TemplateOutput;
+ updateTemplate: TemplateOutput;
+ addPrototype: TemplateProtoTypeOutput;
+ updatePrototype: TemplateProtoTypeOutput;
+ addProject: ProjectOutput;
+ changeProjectState: ProjectOutput;
+ updateProject: ProjectOutput;
+ addProjectProposal: ProjectOutput;
+ addProjectMvp: ProjectOutput;
+ addProjectFullBuild: ProjectOutput;
+ addProjectDesign: ProjectOutput;
+};
+
+export type MutationRootSignupArgs = {
+ email: Scalars['String'];
+ password: Scalars['String'];
+};
+
+export type MutationRootCreateUserArgs = {
+ user: UserInput;
+};
+
+export type MutationRootLoginArgs = {
+ email: Scalars['String'];
+ password: Scalars['String'];
+};
+
+export type MutationRootDeleteUserArgs = {
+ id: Scalars['String'];
+ password: Scalars['String'];
+};
+
+export type MutationRootUpdateUserInfoArgs = {
+ user: UpdateUserInput;
+};
+
+export type MutationRootUpdateUserPasswordArgs = {
+ id: Scalars['String'];
+ password: PasswordInput;
+};
+
+export type MutationRootResetUserPasswordArgs = {
+ email: Scalars['String'];
+};
+
+export type MutationRootConfirmUserResetPasswordArgs = {
+ id: Scalars['String'];
+ password: Scalars['String'];
+};
+
+export type MutationRootDeleteCategoryArgs = {
+ id: Scalars['String'];
+};
+
+export type MutationRootDeleteFeatureArgs = {
+ id: Scalars['String'];
+};
+
+export type MutationRootDeleteTemplateArgs = {
+ id: Scalars['String'];
+};
+
+export type MutationRootUpdateTemplateFeaturesArgs = {
+ id: Scalars['String'];
+ featuresId: Array;
+};
+
+export type MutationRootAddTemplateSpecificationArgs = {
+ id: Scalars['String'];
+ specification: SpecificationInput;
+};
+
+export type MutationRootAddCategoryArgs = {
+ category: CategoryInput;
+};
+
+export type MutationRootUpdateCategoryArgs = {
+ id: Scalars['String'];
+ category: CategoryInput;
+};
+
+export type MutationRootAddFeatureArgs = {
+ feature: FeatureInput;
+};
+
+export type MutationRootUpdateFeatureArgs = {
+ id: Scalars['String'];
+ feature: FeatureInput;
+};
+
+export type MutationRootDeleteFeatureWireframeArgs = {
+ id: Scalars['String'];
+};
+
+export type MutationRootAddFeatureWireframesArgs = {
+ id: Scalars['String'];
+ wireframes: Array;
+};
+
+export type MutationRootAddTemplateArgs = {
+ template: TemplateInput;
+};
+
+export type MutationRootUpdateTemplateArgs = {
+ id: Scalars['String'];
+ template: TemplateUpdateInput;
+ specification?: Maybe;
+};
+
+export type MutationRootAddPrototypeArgs = {
+ prototype: TemplateProtoTypeInput;
+};
+
+export type MutationRootUpdatePrototypeArgs = {
+ prototype: TemplateProtoTypeInput;
+};
+
+export type MutationRootAddProjectArgs = {
+ project: ProjectInput;
+};
+
+export type MutationRootChangeProjectStateArgs = {
+ id: Scalars['String'];
+ state: State;
+};
+
+export type MutationRootUpdateProjectArgs = {
+ id: Scalars['String'];
+ name: Scalars['String'];
+ image: InputFile;
+};
+
+export type MutationRootAddProjectProposalArgs = {
+ id: Scalars['String'];
+ proposal: ProposalInput;
+};
+
+export type MutationRootAddProjectMvpArgs = {
+ mvp: ProjectFileInput;
+};
+
+export type MutationRootAddProjectFullBuildArgs = {
+ fullBuild: ProjectFullBuildInput;
+};
+
+export type MutationRootAddProjectDesignArgs = {
+ design: ProjectFileInput;
+};
+
+export type NonFunctionalRequirementsInput = {
+ performanceRequirements: Scalars['String'];
+ safetyRequirements: Scalars['String'];
+ securityRequirements: Scalars['String'];
+ softwareQualityAttributes: Scalars['String'];
+};
+
+export type NonFunctionalRequirementsOutput = {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ performanceRequirements: Scalars['String'];
+ safetyRequirements: Scalars['String'];
+ securityRequirements: Scalars['String'];
+ softwareQualityAttributes: Scalars['String'];
+};
+
+export type OverallDescriptionInput = {
+ perspective: Scalars['String'];
+ userCharacteristics: Scalars['String'];
+ operatingEnvironment: Scalars['String'];
+ designImplementationConstraints: Scalars['String'];
+ userDocumentation: Scalars['String'];
+ assemptionsDependencies: Scalars['String'];
+};
+
+export type OverallDescriptionOutput = {
+ __typename?: 'OverallDescriptionOutput';
+ perspective: Scalars['String'];
+ userCharacteristics: Scalars['String'];
+ operatingEnvironment: Scalars['String'];
+ designImplementationConstraints: Scalars['String'];
+ userDocumentation: Scalars['String'];
+ assemptionsDependencies: Scalars['String'];
+};
+
+export type PasswordInput = {
+ oldPassword: Scalars['String'];
+ newPassword: Scalars['String'];
+};
+
+export type PaymentOptionInput = {
+ optOne: Scalars['Int'];
+ optTwo: Scalars['Int'];
+ optThree: Scalars['Int'];
+};
+
+export type PaymentOptionOutput = {
+ __typename?: 'PaymentOptionOutput';
+ optOne: Scalars['Int'];
+ optTwo: Scalars['Int'];
+ optThree: Scalars['Int'];
+};
+
+export type PhoneInput = {
+ prefix: Scalars['String'];
+ number: Scalars['String'];
+};
+
+export type PhoneOutput = {
+ __typename?: 'PhoneOutput';
+ prefix: Scalars['String'];
+ number: Scalars['String'];
+};
+
+export type ProjectFileInput = {
+ id: Scalars['String'];
+ name: Scalars['String'];
+ src: Scalars['String'];
+};
+
+export type ProjectFullBuildInput = {
+ id: Scalars['String'];
+ url: Scalars['String'];
+};
+
+export type ProjectInput = {
+ clientId: Scalars['String'];
+ name: Scalars['String'];
+ image: InputFile;
+ platforms: Array;
+ template: Scalars['String'];
+ features: Array;
+ paymentOption: PaymentOptionInput;
+ delivrable?: Maybe;
+ totalPrice: Scalars['Float'];
+};
+
+export type ProjectOutput = {
+ __typename?: 'ProjectOutput';
+ id: Scalars['String'];
+ clientId: Scalars['String'];
+ name: Scalars['String'];
+ image: File;
+ platforms: Array;
+ template: TemplateOutput;
+ features: Array;
+ state: Scalars['String'];
+ proposal?: Maybe;
+ paymentOption: PaymentOptionOutput;
+ delivrable?: Maybe;
+ totalPrice: Scalars['Float'];
+};
+
+export type ProposalInput = {
+ devtime: DevtimeInput;
+ summary: Scalars['String'];
+ purpose: Scalars['String'];
+ resources: Array;
+};
+
+export type ProposalOutput = {
+ __typename?: 'ProposalOutput';
+ devtime: DevtimeOutput;
+ summary: Scalars['String'];
+ purpose: Scalars['String'];
+ resources: Array;
+};
+
+export type ProtoTypeInput = {
+ featureId: Scalars['String'];
+ connections: Array;
+};
+
+export type ProtoTypeOutput = {
+ __typename?: 'ProtoTypeOutput';
+ feature: FeatureOutput;
+ connections: Array;
+};
+
+export type QueryRoot = {
+ __typename?: 'QueryRoot';
+ getAllUsers: Array;
+ getUserById: UserOutput;
+ getCategoryById: CategoryOutput;
+ getFeatureById: FeatureOutput;
+ getPrototypeById: TemplateProtoTypeOutput;
+ getTemplateById: TemplateOutput;
+ getProjectById: ProjectOutput;
+ getAllProjectsByClientId: Array;
+ getAllProjects: Array;
+ getAllCategories: Array;
+ getAllFeatures: Array;
+ getAllTemplates: Array;
+ getAllTemplatesByCategoriesId: Array;
+ getCountryCode: Array;
+};
+
+export type QueryRootGetUserByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetCategoryByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetFeatureByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetPrototypeByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetTemplateByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetProjectByIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetAllProjectsByClientIdArgs = {
+ id: Scalars['String'];
+};
+
+export type QueryRootGetAllTemplatesByCategoriesIdArgs = {
+ categories: Array;
+};
+
+export type RelationsInput = {
+ back: Scalars['Boolean'];
+ forword: Scalars['Boolean'];
+};
+
+export type RelationsOutput = {
+ __typename?: 'RelationsOutput';
+ back: Scalars['Boolean'];
+ forword: Scalars['Boolean'];
+};
+
+export type ResourceInput = {
+ resourceType: Scalars['String'];
+ developers: Scalars['Int'];
+};
+
+export type ResourceOutput = {
+ __typename?: 'ResourceOutput';
+ resourceType: Scalars['String'];
+ developers: Scalars['Int'];
+};
+
+export type SpecificationInput = {
+ introduction: IntroductionInput;
+ overallDescription: OverallDescriptionInput;
+ nonFunctionalRequirements: NonFunctionalRequirementsInput;
+ otherRequirements: Scalars['String'];
+ glossary: Scalars['String'];
+ analysisModels: Scalars['String'];
+ issuesList: Scalars['String'];
+};
+
+export type SpecificationOutput = {
+ __typename?: 'SpecificationOutput';
+ introduction: IntroductionOutput;
+ overallDescription: OverallDescriptionOutput;
+ nonFunctionalRequirements: NonFunctionalRequirementsOutput;
+ otherRequirements: Scalars['String'];
+ glossary: Scalars['String'];
+ analysisModels: Scalars['String'];
+ issuesList: Scalars['String'];
+};
+
+export type State = 'Approved' | 'Declined' | 'OnReview' | 'Archived';
+
+export type TemplateDefactoredOutput = {
+ __typename?: 'TemplateDefactoredOutput';
+ id: Scalars['String'];
+ name: Scalars['String'];
+ description: Scalars['String'];
+ category: Scalars['String'];
+ features?: Maybe>;
+ image: File;
+ specification?: Maybe;
+};
+
+export type TemplateInput = {
+ name: Scalars['String'];
+ description: Scalars['String'];
+ category: Scalars['String'];
+ features?: Maybe>;
+ image: InputFile;
+ specification?: Maybe;
+};
+
+export type TemplateOutput = {
+ __typename?: 'TemplateOutput';
+ id: Scalars['String'];
+ name: Scalars['String'];
+ description: Scalars['String'];
+ category: Scalars['String'];
+ features?: Maybe>;
+ image: File;
+ specification?: Maybe;
+};
+
+export type TemplateProtoTypeInput = {
+ templateId: Scalars['String'];
+ prototype: Array;
+};
+
+export type TemplateProtoTypeOutput = {
+ __typename?: 'TemplateProtoTypeOutput';
+ id: Scalars['String'];
+ template: Scalars['String'];
+ prototype: Array;
+};
+
+export type TemplateUpdateInput = {
+ name: Scalars['String'];
+ description: Scalars['String'];
+ category: Scalars['String'];
+ features?: Maybe>;
+ image: InputFile;
+};
+
+export type UpdateUserInput = {
+ id: Scalars['String'];
+ email: Scalars['String'];
+ firstName: Scalars['String'];
+ lastName: Scalars['String'];
+ phone: PhoneInput;
+ address: AddressInput;
+ role: Scalars['String'];
+};
+
+export type UserAuthenticationOutput = {
+ __typename?: 'UserAuthenticationOutput';
+ user: UserOutput;
+ token: Scalars['String'];
+};
+
+export type UserInput = {
+ email: Scalars['String'];
+ password: Scalars['String'];
+ firstName: Scalars['String'];
+ lastName: Scalars['String'];
+ phone: PhoneInput;
+ address: AddressInput;
+ role: Scalars['String'];
+};
+
+export type UserOutput = {
+ __typename?: 'UserOutput';
+ id: Scalars['String'];
+ email: Scalars['String'];
+ firstName: Scalars['String'];
+ lastName: Scalars['String'];
+ phone: PhoneOutput;
+ address: AddressOutput;
+ role: Scalars['String'];
+};
+
+export type GetAllUsersQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetAllUsersQuery = { __typename?: 'QueryRoot' } & {
+ getAllUsers: Array<
+ { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ }
+ >;
+};
+
+export type GetUserByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetUserByIdQuery = { __typename?: 'QueryRoot' } & {
+ getUserById: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type CreateUserMutationVariables = Exact<{
+ user: UserInput;
+}>;
+
+export type CreateUserMutation = { __typename?: 'MutationRoot' } & {
+ createUser: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type SignupMutationVariables = Exact<{
+ email: Scalars['String'];
+ password: Scalars['String'];
+}>;
+
+export type SignupMutation = { __typename?: 'MutationRoot' } & {
+ signup: { __typename?: 'UserAuthenticationOutput' } & Pick<
+ UserAuthenticationOutput,
+ 'token'
+ > & {
+ user: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+ };
+};
+
+export type LoginMutationVariables = Exact<{
+ email: Scalars['String'];
+ password: Scalars['String'];
+}>;
+
+export type LoginMutation = { __typename?: 'MutationRoot' } & {
+ login: { __typename?: 'UserAuthenticationOutput' } & Pick<
+ UserAuthenticationOutput,
+ 'token'
+ > & {
+ user: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+ };
+};
+
+export type ResetPasswordMutationVariables = Exact<{
+ email: Scalars['String'];
+}>;
+
+export type ResetPasswordMutation = { __typename?: 'MutationRoot' } & {
+ resetUserPassword: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type ConfirmUserResetPasswordMutationVariables = Exact<{
+ id: Scalars['String'];
+ password: Scalars['String'];
+}>;
+
+export type ConfirmUserResetPasswordMutation = {
+ __typename?: 'MutationRoot';
+} & {
+ confirmUserResetPassword: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type UpdateUserInfoMutationVariables = Exact<{
+ user: UpdateUserInput;
+}>;
+
+export type UpdateUserInfoMutation = { __typename?: 'MutationRoot' } & {
+ updateUserInfo: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type UpdateUserPasswordMutationVariables = Exact<{
+ id: Scalars['String'];
+ password: PasswordInput;
+}>;
+
+export type UpdateUserPasswordMutation = { __typename?: 'MutationRoot' } & {
+ updateUserPassword: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type DeleteUserMutationVariables = Exact<{
+ id: Scalars['String'];
+ password: Scalars['String'];
+}>;
+
+export type DeleteUserMutation = { __typename?: 'MutationRoot' } & {
+ deleteUser: { __typename?: 'UserOutput' } & Pick<
+ UserOutput,
+ 'id' | 'email' | 'firstName' | 'lastName' | 'role'
+ > & {
+ phone: { __typename?: 'PhoneOutput' } & Pick<
+ PhoneOutput,
+ 'prefix' | 'number'
+ >;
+ address: { __typename?: 'AddressOutput' } & Pick<
+ AddressOutput,
+ 'place' | 'city' | 'country' | 'zip'
+ >;
+ };
+};
+
+export type GetCountryCodesQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetCountryCodesQuery = { __typename?: 'QueryRoot' } & {
+ getCountryCode: Array<
+ { __typename?: 'CountryPrefixModel' } & Pick<
+ CountryPrefixModel,
+ 'prefix' | 'country'
+ >
+ >;
+};
+
+export type GetAllCategoriesQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetAllCategoriesQuery = { __typename?: 'QueryRoot' } & {
+ getAllCategories: Array<
+ { __typename?: 'CategoryOutput' } & Pick<
+ CategoryOutput,
+ 'id' | 'name' | 'description'
+ > & { image: { __typename?: 'File' } & Pick }
+ >;
+};
+
+export type GetCategoryByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetCategoryByIdQuery = { __typename?: 'QueryRoot' } & {
+ getCategoryById: { __typename?: 'CategoryOutput' } & Pick<
+ CategoryOutput,
+ 'id' | 'name' | 'description'
+ > & { image: { __typename?: 'File' } & Pick };
+};
+
+export type AddCategoryMutationVariables = Exact<{
+ category: CategoryInput;
+}>;
+
+export type AddCategoryMutation = { __typename?: 'MutationRoot' } & {
+ addCategory: { __typename?: 'CategoryOutput' } & Pick<
+ CategoryOutput,
+ 'id' | 'name' | 'description'
+ > & { image: { __typename?: 'File' } & Pick };
+};
+
+export type UpdateCategoryMutationVariables = Exact<{
+ id: Scalars['String'];
+ category: CategoryInput;
+}>;
+
+export type UpdateCategoryMutation = { __typename?: 'MutationRoot' } & {
+ updateCategory: { __typename?: 'CategoryOutput' } & Pick<
+ CategoryOutput,
+ 'id' | 'name' | 'description'
+ > & { image: { __typename?: 'File' } & Pick };
+};
+
+export type DeleteCategoryMutationVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type DeleteCategoryMutation = { __typename?: 'MutationRoot' } & {
+ deleteCategory: { __typename?: 'CategoryOutput' } & Pick<
+ CategoryOutput,
+ 'id' | 'name' | 'description'
+ > & { image: { __typename?: 'File' } & Pick };
+};
+
+export type GetAllFeaturesQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetAllFeaturesQuery = { __typename?: 'QueryRoot' } & {
+ getAllFeatures: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+};
+
+export type GetFeatureByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetFeatureByIdQuery = { __typename?: 'QueryRoot' } & {
+ getFeatureById: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type AddFeatureMutationVariables = Exact<{
+ feature: FeatureInput;
+}>;
+
+export type AddFeatureMutation = { __typename?: 'MutationRoot' } & {
+ addFeature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type UpdateFeatureMutationVariables = Exact<{
+ id: Scalars['String'];
+ feature: FeatureInput;
+}>;
+
+export type UpdateFeatureMutation = { __typename?: 'MutationRoot' } & {
+ updateFeature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type DeleteFeatureMutationVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type DeleteFeatureMutation = { __typename?: 'MutationRoot' } & {
+ deleteFeature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type AddFeatureWireframesMutationVariables = Exact<{
+ id: Scalars['String'];
+ wireframes: Array | InputFile;
+}>;
+
+export type AddFeatureWireframesMutation = { __typename?: 'MutationRoot' } & {
+ addFeatureWireframes: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type DeleteFeatureWireframeMutationVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type DeleteFeatureWireframeMutation = { __typename?: 'MutationRoot' } & {
+ deleteFeatureWireframe: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+};
+
+export type GetAllProjectsQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetAllProjectsQuery = { __typename?: 'QueryRoot' } & {
+ getAllProjects: Array<
+ { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ | 'id'
+ | 'name'
+ | 'description'
+ | 'featureType'
+ | 'price'
+ | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ | 'otherRequirements'
+ | 'glossary'
+ | 'analysisModels'
+ | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick<
+ File,
+ 'name' | 'src'
+ >;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ }
+ >;
+};
+
+export type GetAllProjectsByClientIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetAllProjectsByClientIdQuery = { __typename?: 'QueryRoot' } & {
+ getAllProjectsByClientId: Array<
+ { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ | 'id'
+ | 'name'
+ | 'description'
+ | 'featureType'
+ | 'price'
+ | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ | 'otherRequirements'
+ | 'glossary'
+ | 'analysisModels'
+ | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick<
+ File,
+ 'name' | 'src'
+ >;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ }
+ >;
+};
+
+export type GetProjectByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetProjectByIdQuery = { __typename?: 'QueryRoot' } & {
+ getProjectById: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type AddProjectMutationVariables = Exact<{
+ project: ProjectInput;
+}>;
+
+export type AddProjectMutation = { __typename?: 'MutationRoot' } & {
+ addProject: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type ChangeProjectStateMutationVariables = Exact<{
+ id: Scalars['String'];
+ state: State;
+}>;
+
+export type ChangeProjectStateMutation = { __typename?: 'MutationRoot' } & {
+ changeProjectState: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type UpdateProjectMutationVariables = Exact<{
+ id: Scalars['String'];
+ name: Scalars['String'];
+ image: InputFile;
+}>;
+
+export type UpdateProjectMutation = { __typename?: 'MutationRoot' } & {
+ updateProject: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type AddProjectProposalMutationVariables = Exact<{
+ id: Scalars['String'];
+ proposal: ProposalInput;
+}>;
+
+export type AddProjectProposalMutation = { __typename?: 'MutationRoot' } & {
+ addProjectProposal: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type AddProjectDesignMutationVariables = Exact<{
+ design: ProjectFileInput;
+}>;
+
+export type AddProjectDesignMutation = { __typename?: 'MutationRoot' } & {
+ addProjectDesign: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type AddProjectMvpMutationVariables = Exact<{
+ mvp: ProjectFileInput;
+}>;
+
+export type AddProjectMvpMutation = { __typename?: 'MutationRoot' } & {
+ addProjectMvp: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type AddProjectFullBuildMutationVariables = Exact<{
+ fullBuild: ProjectFullBuildInput;
+}>;
+
+export type AddProjectFullBuildMutation = { __typename?: 'MutationRoot' } & {
+ addProjectFullBuild: { __typename?: 'ProjectOutput' } & Pick<
+ ProjectOutput,
+ 'id' | 'clientId' | 'name' | 'platforms' | 'state' | 'totalPrice'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ template: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+ features: Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >;
+ proposal?: Maybe<
+ { __typename?: 'ProposalOutput' } & Pick<
+ ProposalOutput,
+ 'summary' | 'purpose'
+ > & {
+ devtime: { __typename?: 'DevtimeOutput' } & Pick<
+ DevtimeOutput,
+ 'months' | 'days' | 'hours'
+ >;
+ resources: Array<
+ { __typename?: 'ResourceOutput' } & Pick<
+ ResourceOutput,
+ 'resourceType' | 'developers'
+ >
+ >;
+ }
+ >;
+ paymentOption: { __typename?: 'PaymentOptionOutput' } & Pick<
+ PaymentOptionOutput,
+ 'optOne' | 'optTwo' | 'optThree'
+ >;
+ delivrable?: Maybe<
+ { __typename?: 'DelivrableOutput' } & Pick<
+ DelivrableOutput,
+ 'fullBuild'
+ > & {
+ specification: { __typename?: 'File' } & Pick;
+ mvp: { __typename?: 'File' } & Pick;
+ design: { __typename?: 'File' } & Pick;
+ }
+ >;
+ };
+};
+
+export type GetPrototypeByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetPrototypeByIdQuery = { __typename?: 'QueryRoot' } & {
+ getPrototypeById: { __typename?: 'TemplateProtoTypeOutput' } & Pick<
+ TemplateProtoTypeOutput,
+ 'id' | 'template'
+ > & {
+ prototype: Array<
+ { __typename?: 'ProtoTypeOutput' } & {
+ feature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+ connections: Array<
+ { __typename?: 'ConnectionsOutput' } & Pick<
+ ConnectionsOutput,
+ 'to'
+ > & {
+ releations: { __typename?: 'RelationsOutput' } & Pick<
+ RelationsOutput,
+ 'back' | 'forword'
+ >;
+ }
+ >;
+ }
+ >;
+ };
+};
+
+export type AddPrototypeMutationVariables = Exact<{
+ prototype: TemplateProtoTypeInput;
+}>;
+
+export type AddPrototypeMutation = { __typename?: 'MutationRoot' } & {
+ addPrototype: { __typename?: 'TemplateProtoTypeOutput' } & Pick<
+ TemplateProtoTypeOutput,
+ 'id' | 'template'
+ > & {
+ prototype: Array<
+ { __typename?: 'ProtoTypeOutput' } & {
+ feature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+ connections: Array<
+ { __typename?: 'ConnectionsOutput' } & Pick<
+ ConnectionsOutput,
+ 'to'
+ > & {
+ releations: { __typename?: 'RelationsOutput' } & Pick<
+ RelationsOutput,
+ 'back' | 'forword'
+ >;
+ }
+ >;
+ }
+ >;
+ };
+};
+
+export type UpdatePrototypeMutationVariables = Exact<{
+ prototype: TemplateProtoTypeInput;
+}>;
+
+export type UpdatePrototypeMutation = { __typename?: 'MutationRoot' } & {
+ updatePrototype: { __typename?: 'TemplateProtoTypeOutput' } & Pick<
+ TemplateProtoTypeOutput,
+ 'id' | 'template'
+ > & {
+ prototype: Array<
+ { __typename?: 'ProtoTypeOutput' } & {
+ feature: { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ };
+ connections: Array<
+ { __typename?: 'ConnectionsOutput' } & Pick<
+ ConnectionsOutput,
+ 'to'
+ > & {
+ releations: { __typename?: 'RelationsOutput' } & Pick<
+ RelationsOutput,
+ 'back' | 'forword'
+ >;
+ }
+ >;
+ }
+ >;
+ };
+};
+
+export type GetAllTemplatesQueryVariables = Exact<{ [key: string]: never }>;
+
+export type GetAllTemplatesQuery = { __typename?: 'QueryRoot' } & {
+ getAllTemplates: Array<
+ { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ }
+ >;
+};
+
+export type GetTemplateByIdQueryVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type GetTemplateByIdQuery = { __typename?: 'QueryRoot' } & {
+ getTemplateById: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
+
+export type GetAllTemplatesByCategoriesIdQueryVariables = Exact<{
+ categories: Array | Scalars['String'];
+}>;
+
+export type GetAllTemplatesByCategoriesIdQuery = {
+ __typename?: 'QueryRoot';
+} & {
+ getAllTemplatesByCategoriesId: Array<
+ { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ }
+ >;
+};
+
+export type AddTemplateMutationVariables = Exact<{
+ template: TemplateInput;
+}>;
+
+export type AddTemplateMutation = { __typename?: 'MutationRoot' } & {
+ addTemplate: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
+
+export type UpdateTemplateMutationVariables = Exact<{
+ id: Scalars['String'];
+ template: TemplateUpdateInput;
+ specification?: Maybe;
+}>;
+
+export type UpdateTemplateMutation = { __typename?: 'MutationRoot' } & {
+ updateTemplate: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
+
+export type UpdateTemplateFeaturesMutationVariables = Exact<{
+ id: Scalars['String'];
+ featuresId: Array | Scalars['String'];
+}>;
+
+export type UpdateTemplateFeaturesMutation = { __typename?: 'MutationRoot' } & {
+ updateTemplateFeatures: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
+
+export type AddTemplateSpecificationMutationVariables = Exact<{
+ id: Scalars['String'];
+ specification: SpecificationInput;
+}>;
+
+export type AddTemplateSpecificationMutation = {
+ __typename?: 'MutationRoot';
+} & {
+ addTemplateSpecification: { __typename?: 'TemplateOutput' } & Pick<
+ TemplateOutput,
+ 'id' | 'name' | 'description' | 'category'
+ > & {
+ features?: Maybe<
+ Array<
+ { __typename?: 'FeatureOutput' } & Pick<
+ FeatureOutput,
+ 'id' | 'name' | 'description' | 'featureType' | 'price' | 'repo'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ wireframes?: Maybe<
+ Array<
+ { __typename?: 'FileWithOutOId' } & Pick<
+ FileWithOutOId,
+ 'id' | 'name' | 'src'
+ >
+ >
+ >;
+ }
+ >
+ >;
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
+
+export type DeleteTemplateMutationVariables = Exact<{
+ id: Scalars['String'];
+}>;
+
+export type DeleteTemplateMutation = { __typename?: 'MutationRoot' } & {
+ deleteTemplate: { __typename?: 'TemplateDefactoredOutput' } & Pick<
+ TemplateDefactoredOutput,
+ 'id' | 'name' | 'description' | 'category' | 'features'
+ > & {
+ image: { __typename?: 'File' } & Pick;
+ specification?: Maybe<
+ { __typename?: 'SpecificationOutput' } & Pick<
+ SpecificationOutput,
+ 'otherRequirements' | 'glossary' | 'analysisModels' | 'issuesList'
+ > & {
+ introduction: { __typename?: 'IntroductionOutput' } & Pick<
+ IntroductionOutput,
+ | 'purpose'
+ | 'documentConventions'
+ | 'intendedAudience'
+ | 'projectScope'
+ >;
+ overallDescription: {
+ __typename?: 'OverallDescriptionOutput';
+ } & Pick<
+ OverallDescriptionOutput,
+ | 'perspective'
+ | 'userCharacteristics'
+ | 'operatingEnvironment'
+ | 'designImplementationConstraints'
+ | 'userDocumentation'
+ | 'assemptionsDependencies'
+ >;
+ nonFunctionalRequirements: {
+ __typename?: 'NonFunctionalRequirementsOutput';
+ } & Pick<
+ NonFunctionalRequirementsOutput,
+ | 'performanceRequirements'
+ | 'safetyRequirements'
+ | 'securityRequirements'
+ | 'softwareQualityAttributes'
+ >;
+ }
+ >;
+ };
+};
diff --git a/src/index.tsx b/src/index.tsx
new file mode 100644
index 0000000..60069ab
--- /dev/null
+++ b/src/index.tsx
@@ -0,0 +1,96 @@
+import React from 'react';
+import * as ReactDOMClient from 'react-dom/client';
+import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
+import { createClient } from 'graphql-ws';
+import {
+ ApolloClient,
+ InMemoryCache,
+ ApolloProvider,
+ split,
+ HttpLink,
+} from '@apollo/client';
+import { getMainDefinition } from '@apollo/client/utilities';
+import { setContext } from '@apollo/client/link/context';
+import { ThemeProvider } from 'styled-components';
+import { BrowserRouter } from 'react-router-dom';
+import { theme } from './themes';
+import App from './App';
+import GlobalStyles from './GlobalStyles';
+import reportWebVitals from './reportWebVitals';
+
+const httpLinkMain = new HttpLink({
+ uri: import.meta.env.VITE_GRAPHQL_API,
+});
+
+const httpLinkSupport = new HttpLink({
+ uri: import.meta.env.VITE_GRAPHQL_SUPPORT_API,
+});
+
+const wsLink = new GraphQLWsLink(
+ createClient({
+ url: `${import.meta.env.VITE_GRAPHQL_SUPPORT_SUBSCRIPTIONS_API}`,
+ })
+);
+
+const splitLink = split(
+ ({ query }) => {
+ const definition = getMainDefinition(query);
+ return (
+ definition.kind === 'OperationDefinition' &&
+ definition.operation === 'subscription'
+ );
+ },
+ wsLink,
+ httpLinkSupport
+);
+
+const authLink = setContext((_, { headers }) => {
+ const token = localStorage.getItem('token');
+
+ return {
+ headers: {
+ ...headers,
+ authorization: token || '',
+ },
+ };
+});
+
+export const clientMain = new ApolloClient({
+ link: authLink.concat(httpLinkMain),
+ cache: new InMemoryCache(),
+});
+
+export const clientSupport = new ApolloClient({
+ link: authLink.concat(splitLink),
+ cache: new InMemoryCache(),
+});
+
+let root: ReactDOMClient.Root | null = null;
+
+document.addEventListener('DOMContentLoaded', () => {
+ if (!root) {
+ root = ReactDOMClient.createRoot(
+ document.querySelector('#app') as HTMLElement
+ );
+
+ root.render(
+
+
+ {/* @ts-ignore */}
+
+
+
+ {/* @ts-ignore */}
+
+
+
+
+
+ );
+ }
+});
+
+// If you want to start measuring performance in your app, pass a function
+// to log results (for example: reportWebVitals(console.log))
+// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
+reportWebVitals();
diff --git a/src/pages/AddCategory/index.tsx b/src/pages/AddCategory/index.tsx
new file mode 100644
index 0000000..f33ef83
--- /dev/null
+++ b/src/pages/AddCategory/index.tsx
@@ -0,0 +1,201 @@
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+import { Navigate, useNavigate } from 'react-router';
+import { useMutation, useReactiveVar } from '@apollo/client';
+import React, { useState } from 'react';
+import { roleVar } from '../../graphql/state';
+import {
+ Box,
+ Button,
+ Text,
+ SectionSelector,
+ Input,
+ Alert,
+ TextArea,
+} from '../../components';
+import { Wrapper } from './styles';
+import { ArrowLeft, General } from '../../assets';
+import {
+ AddCategoryMutation,
+ AddCategoryMutationVariables,
+} from '../../graphql/types';
+import { ADD_CATEGORY } from '../../graphql/category.api';
+
+const AddCategory = () => {
+ const navigate = useNavigate();
+ const role = useReactiveVar(roleVar);
+
+ const [error, setError] = useState('');
+
+ const [addCategory, { loading }] = useMutation<
+ AddCategoryMutation,
+ AddCategoryMutationVariables
+ >(ADD_CATEGORY, {
+ onCompleted({ addCategory: { id } }) {
+ navigate(`/category/${id}`);
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const form = useFormik({
+ initialValues: {
+ name: '',
+ description: '',
+ imageName: '',
+ imageSource: '',
+ },
+ validationSchema: Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ description: Yup.string().required('Description is required'),
+ imageName: Yup.string().required('Image is required'),
+ imageSource: Yup.string().required('Image is required'),
+ }),
+ onSubmit: ({ name, description, imageName, imageSource }) => {
+ addCategory({
+ variables: {
+ category: {
+ name,
+ description,
+ image: { name: imageName, src: imageSource },
+ },
+ },
+ });
+ },
+ });
+
+ if (role !== 'developer') return (
+ <>
+ {role === 'admin' && }
+ {['client', 'productOwer'].includes(role as string) && }
+ >
+ )
+
+ return (
+
+
+
+
+
+ }
+ color={role || 'client'}
+ text='General'
+ selected
+ />
+
+
+
+
+ General
+
+ {error && }
+
+
+
+
+
+ );
+};
+
+export default AddCategory;
diff --git a/src/pages/AddCategory/styles.ts b/src/pages/AddCategory/styles.ts
new file mode 100644
index 0000000..a1193e9
--- /dev/null
+++ b/src/pages/AddCategory/styles.ts
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+ padding: 35px 45px 35px 120px;
+`;
diff --git a/src/pages/AddFeature/index.tsx b/src/pages/AddFeature/index.tsx
new file mode 100644
index 0000000..1bba077
--- /dev/null
+++ b/src/pages/AddFeature/index.tsx
@@ -0,0 +1,489 @@
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+import { Navigate, useNavigate } from 'react-router';
+import { useMutation, useReactiveVar } from '@apollo/client';
+import React, { useState } from 'react';
+import { roleVar } from '../../graphql/state';
+import {
+ Box,
+ Button,
+ Text,
+ SectionSelector,
+ Input,
+ Alert,
+ TextArea,
+ CheckBox,
+ ImagePreview,
+} from '../../components';
+import { Wrapper } from './styles';
+import { ArrowLeft, General, Design } from '../../assets';
+import {
+ AddFeatureMutation,
+ AddFeatureMutationVariables,
+} from '../../graphql/types';
+import { ADD_FEATURE } from '../../graphql/feature.api';
+
+const AddFeature = () => {
+ const navigate = useNavigate();
+ const role = useReactiveVar(roleVar);
+ const [newFeature, setNewFeature] = useState<{
+ name: string;
+ description: string;
+ featureType: string;
+ image: {
+ name: string;
+ src: string;
+ };
+ wireframes?: Array<{
+ name: string;
+ src: string;
+ }>;
+ price: number;
+ repo: string;
+ }>({
+ name: '',
+ description: '',
+ featureType: '',
+ image: {
+ name: '',
+ src: '',
+ },
+ price: 0,
+ repo: '',
+ });
+
+ const [selectedSection, setSelectedSection] = useState<
+ 'general' | 'wireframes'
+ >('general');
+ const [error, setError] = useState('');
+
+ const [addFeature, { loading }] = useMutation<
+ AddFeatureMutation,
+ AddFeatureMutationVariables
+ >(ADD_FEATURE, {
+ onCompleted({ addFeature: { id } }) {
+ navigate(`/feature/${id}`);
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const generalForm = useFormik({
+ initialValues: {
+ name: '',
+ description: '',
+ imageName: '',
+ imageSource: '',
+ featureType: '',
+ price: '',
+ repo: '',
+ },
+ validationSchema: Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ description: Yup.string().required('Description is required'),
+ imageName: Yup.string().required('Image is required'),
+ imageSource: Yup.string().required('Image is required'),
+ featureType: Yup.string().required('Feature Type is required'),
+ price: Yup.number().typeError('Price must be a number').required('Price is required'),
+ repo: Yup.string().required('Repo is required'),
+ }),
+ onSubmit: ({
+ name,
+ description,
+ imageName,
+ imageSource,
+ featureType,
+ price,
+ repo,
+ }) => {
+ setNewFeature({
+ name,
+ description,
+ featureType,
+ image: { name: imageName, src: imageSource },
+ price: parseFloat(price),
+ repo,
+ });
+ setSelectedSection('wireframes');
+ },
+ });
+
+ const wireframesForm = useFormik<{
+ wireframes: Array<{ name: string; src: string }>;
+ }>({
+ initialValues: {
+ wireframes: [],
+ },
+ onSubmit: ({ wireframes }) => {
+ addFeature({ variables: { feature: { ...newFeature, wireframes } } });
+ },
+ });
+
+ if (role !== 'developer') return (
+ <>
+ {role === 'admin' && }
+ {['client', 'productOwer'].includes(role as string) && }
+ >
+ )
+
+ return (
+
+
+
+
+
+ }
+ color={role || 'client'}
+ text='General'
+ selected={selectedSection === 'general'}
+ />
+ }
+ color={role || 'client'}
+ text='Wireframes'
+ selected={selectedSection === 'wireframes'}
+ />
+
+
+ {selectedSection === 'general' && (
+ <>
+
+
+ {selectedSection === 'general' ? 'General' : 'Wireframes'}
+
+ {error && }
+
+
+ >
+ )}
+ {selectedSection === 'wireframes' && (
+ <>
+
+
+ Wireframes
+
+ {error && }
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default AddFeature;
diff --git a/src/pages/AddFeature/styles.ts b/src/pages/AddFeature/styles.ts
new file mode 100644
index 0000000..ffc89a8
--- /dev/null
+++ b/src/pages/AddFeature/styles.ts
@@ -0,0 +1,11 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+ padding: 35px 45px 35px 120px;
+
+ .feature-type {
+ background: ${({ theme }) => theme.colors.gray.dark};
+ -webkit-background-clip: text;
+ -webkit-text-fill-color: transparent;
+ }
+`;
diff --git a/src/pages/AddProject/index.tsx b/src/pages/AddProject/index.tsx
new file mode 100644
index 0000000..d4a9b9b
--- /dev/null
+++ b/src/pages/AddProject/index.tsx
@@ -0,0 +1,1971 @@
+// @ts-ignore
+import Carousel, { consts } from 'react-elastic-carousel';
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+import { useNavigate } from 'react-router';
+import { useLazyQuery, useMutation, useReactiveVar } from '@apollo/client';
+import { useState, useEffect } from 'react';
+import { roleVar, userVar } from '../../graphql/state';
+import { Wrapper } from './styles';
+import {
+ Alert,
+ Box,
+ Button,
+ CategoryCard,
+ FeatureCard,
+ Input,
+ SectionSelector,
+ Select,
+ Spinner,
+ TemplateCard,
+ Text,
+ TextArea,
+} from '../../components';
+import {
+ ArrowLeft,
+ ArrowRight,
+ ChevronLeft,
+ ChevronRight,
+ Profile,
+ Security,
+} from '../../assets';
+import {
+ AddProjectMutation,
+ AddProjectMutationVariables,
+ AddProjectProposalMutation,
+ AddProjectProposalMutationVariables,
+ CategoryOutput,
+ CountryPrefixModel,
+ CreateUserMutation,
+ CreateUserMutationVariables,
+ DelivrableInput,
+ FeatureOutput,
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables,
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables,
+ GetAllTemplatesByCategoriesIdQuery,
+ GetAllTemplatesByCategoriesIdQueryVariables,
+ GetAllUsersQuery,
+ GetAllUsersQueryVariables,
+ GetCountryCodesQuery,
+ GetCountryCodesQueryVariables,
+ PaymentOptionInput,
+ ProjectInput,
+ TemplateOutput,
+ UserOutput,
+} from '../../graphql/types';
+import { theme } from '../../themes';
+import { GET_ALL_CATEGORIES } from '../../graphql/category.api';
+import { GET_ALL_TEMPLATES_BY_CATEGORIES_ID } from '../../graphql/template.api';
+import { GET_ALL_FEATURES } from '../../graphql/feature.api';
+import { ADD_PROJECT, ADD_PROJECT_PROPOSAL } from '../../graphql/project.api';
+import { CREATE_USER, GET_ALL_USERS } from '../../graphql/admin.api';
+import { GET_COUNTRY_CODES } from '../../graphql/auth.api';
+
+const AddProject = () => {
+ const navigate = useNavigate();
+ const role = useReactiveVar(roleVar);
+ const currentUser = useReactiveVar(userVar);
+ const [error, setError] = useState('');
+ const [step, setStep] = useState<
+ | 'basic-info'
+ | 'categories'
+ | 'template'
+ | 'features'
+ | 'deliverables-platforms'
+ | 'payment-options'
+ | 'client-creation'
+ | 'project-metadata'
+ >('basic-info');
+ const [project, setProject] = useState>();
+ const [chosenCategories, setChosenCategories] = useState>([]);
+ const [chosenTemplate, setChosenTemplate] = useState<
+ TemplateOutput | undefined
+ >();
+ const [chosenFeatures, setChosenFeatures] = useState>(
+ []
+ );
+ const [chosenDeliverables, setChosenDeliverables] =
+ useState();
+ const [chosenPaymentOption, setChosenPaymentOption] =
+ useState();
+ const [chosenPlatforms, setChosenPlatforms] = useState>([]);
+ const [selectedFeature, setSelectedFeature] = useState();
+ const [categories, setCategories] = useState>([]);
+ const [templates, setTemplates] = useState>([]);
+ const [features, setFeatures] = useState>([]);
+ const [newUser, setNewUser] = useState<{
+ firstName: string;
+ lastName: string;
+ email: string;
+ password: string;
+ phone: {
+ prefix: string;
+ number: string;
+ };
+ address: {
+ place: string;
+ city: string;
+ country: string;
+ zip: string;
+ };
+ role: 'Client' | 'ProductOwner' | 'Developer';
+ }>({
+ firstName: '',
+ lastName: '',
+ email: '',
+ password: '',
+ phone: {
+ prefix: '',
+ number: '',
+ },
+ address: {
+ place: '',
+ city: '',
+ country: '',
+ zip: '',
+ },
+ role: 'Client',
+ });
+ const [selectedSection, setSelectedSection] = useState<
+ 'general' | 'security'
+ >('general');
+ const [countryCodes, setCountryCodes] = useState>(
+ []
+ );
+ const [client, setClient] = useState();
+ const [proposal, setProposal] = useState<{
+ devtime: {
+ months: number;
+ days: number;
+ hours: number;
+ };
+ summary: string;
+ purpose: string;
+ resources: Array<{ resourceType: string; developers: number }>;
+ }>();
+ const [developers, setDevelopers] = useState>([]);
+
+ const [getCategories, { loading: categoriesLoading }] = useLazyQuery<
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables
+ >(GET_ALL_CATEGORIES, {
+ onCompleted({ getAllCategories }) {
+ setCategories(getAllCategories);
+ }
+ });
+
+ const [getTemplates, { loading: templatesLoading }] = useLazyQuery<
+ GetAllTemplatesByCategoriesIdQuery,
+ GetAllTemplatesByCategoriesIdQueryVariables
+ >(GET_ALL_TEMPLATES_BY_CATEGORIES_ID, {
+ onCompleted({ getAllTemplatesByCategoriesId }) {
+ setTemplates(getAllTemplatesByCategoriesId);
+ }
+ });
+
+ const [getFeatures, { loading: featuresLoading }] = useLazyQuery<
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables
+ >(GET_ALL_FEATURES, {
+ onCompleted({ getAllFeatures }) {
+ setFeatures(getAllFeatures);
+ }
+ });
+
+ const [getDevelopers, { loading: developersLoading }] = useLazyQuery<
+ GetAllUsersQuery,
+ GetAllUsersQueryVariables
+ >(GET_ALL_USERS, {
+ onCompleted({ getAllUsers }) {
+ setDevelopers(getAllUsers.filter((user) => user.role === 'Developer'));
+ }
+ });
+
+ const [createUser, { loading: createUserLoading }] = useMutation<
+ CreateUserMutation,
+ CreateUserMutationVariables
+ >(CREATE_USER, {
+ onCompleted({ createUser: createdUser }) {
+ setClient(createdUser);
+ setStep('project-metadata');
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const [addProjectProposal, { loading: addProjectProposalLoading }] =
+ useMutation<
+ AddProjectProposalMutation,
+ AddProjectProposalMutationVariables
+ >(ADD_PROJECT_PROPOSAL, {
+ onCompleted({ addProjectProposal: proposalData }) {
+ navigate(`/project/${proposalData.id}`);
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0].extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const [addProject, { loading: addProjectLoading }] = useMutation<
+ AddProjectMutation,
+ AddProjectMutationVariables
+ >(ADD_PROJECT, {
+ onCompleted({ addProject: projectData }) {
+ if (role === 'client') navigate(`/project/${projectData.id}`);
+ else {
+ addProjectProposal({
+ variables: {
+ id: projectData.id,
+ proposal: {
+ devtime: proposal?.devtime!,
+ resources: proposal?.resources!,
+ summary: proposal?.summary!,
+ purpose: proposal?.summary!,
+ },
+ },
+ });
+ }
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0].extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ useEffect(() => {
+ if (step === 'categories') getCategories();
+ if (step === 'template')
+ getTemplates({ variables: { categories: chosenCategories } });
+ if (step === 'features') getFeatures();
+ if (step === 'client-creation') getCountryCodes();
+ if (step === 'project-metadata') getDevelopers();
+ }, [step]);
+
+ const basicInfoForm = useFormik({
+ initialValues: {
+ name: '',
+ imageName: '',
+ imageSource: '',
+ },
+ validationSchema: Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ imageName: Yup.string().required('Image is required'),
+ imageSource: Yup.string().required('Image is required'),
+ }),
+ onSubmit: ({ name, imageName, imageSource }) => {
+ setProject({
+ ...project,
+ clientId: currentUser?.id,
+ name,
+ image: { name: imageName, src: imageSource },
+ });
+ setStep('categories');
+ },
+ });
+
+ const categoriesForm = useFormik<{ selectedCategories: Array }>({
+ initialValues: {
+ selectedCategories: [],
+ },
+ onSubmit: ({ selectedCategories }) => {
+ if (selectedCategories.length === 0) {
+ setError('Select at least one category');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setChosenCategories(selectedCategories);
+ setStep('template');
+ },
+ });
+
+ const templateForm = useFormik<{
+ selectedTemplate: TemplateOutput | undefined;
+ }>({
+ initialValues: {
+ selectedTemplate: undefined,
+ },
+ onSubmit: ({ selectedTemplate }) => {
+ if (!selectedTemplate) {
+ setError('You must select one template');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setChosenTemplate(selectedTemplate);
+ setStep('features');
+ },
+ });
+
+ const featuresForm = useFormik<{
+ selectedFeatures: Array;
+ }>({
+ initialValues: {
+ selectedFeatures: [],
+ },
+ onSubmit: ({ selectedFeatures }) => {
+ if (selectedFeatures.length === 0) {
+ setError('Select at least one feature');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setChosenFeatures(selectedFeatures);
+ setStep('deliverables-platforms');
+ },
+ });
+
+ const deliverablesPlatformsForm = useFormik<{
+ selectedDeliverables: DelivrableInput;
+ selectedPlatforms: Array;
+ }>({
+ initialValues: {
+ selectedDeliverables: {
+ specification: false,
+ design: false,
+ mvp: false,
+ fullBuild: false,
+ },
+ selectedPlatforms: [],
+ },
+ onSubmit: ({ selectedDeliverables, selectedPlatforms }) => {
+ if (
+ Object.values(selectedDeliverables)
+ .slice(0, 4)
+ .filter((value) => value === true).length === 0
+ ) {
+ setError('Select at least one deliverable');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ if (selectedPlatforms.length === 0) {
+ setError('Select at least one platform');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setChosenPlatforms(selectedPlatforms);
+ setChosenDeliverables(selectedDeliverables);
+ setStep('payment-options');
+ },
+ });
+
+ const paymentOptionsForm = useFormik<{
+ optOne: number;
+ optTwo: number;
+ optThree: number;
+ }>({
+ initialValues: {
+ optOne: 0,
+ optTwo: 0,
+ optThree: 0,
+ },
+ onSubmit: ({ optOne, optTwo, optThree }) => {
+ if (!optOne && !optTwo && !optThree) {
+ setError('You must choose one payment option');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setChosenPaymentOption({ optOne, optTwo, optThree });
+ if (role === 'client')
+ addProject({
+ variables: {
+ project: {
+ name: project?.name!,
+ image: {
+ name: project?.image?.name!,
+ src: project?.image?.src!,
+ },
+ features: chosenFeatures.map((feature) => feature.id)!,
+ template: chosenTemplate?.id!,
+ clientId: currentUser?.id!,
+ platforms: chosenPlatforms,
+ delivrable: chosenDeliverables,
+ paymentOption: {
+ optOne,
+ optTwo,
+ optThree,
+ }!,
+ totalPrice: chosenFeatures.reduce(
+ (accumulator, feature) => accumulator + feature.price,
+ 0
+ ),
+ },
+ },
+ });
+
+ if (role === 'productOwner') setStep('client-creation');
+ },
+ });
+
+ const clientCreationGeneralForm = useFormik({
+ initialValues: {
+ firstName: '',
+ lastName: '',
+ email: '',
+ prefix: '',
+ number: '',
+ place: '',
+ city: '',
+ zip: '',
+ country: '',
+ },
+ validationSchema: Yup.object().shape({
+ firstName: Yup.string().required('First Name is required'),
+ lastName: Yup.string().required('Last Name is required'),
+ email: Yup.string()
+ .required('Email is required')
+ .email('Email is invalid'),
+ prefix: Yup.string().required('Prefix is required'),
+ number: Yup.number().typeError('Phone must be a number').required('Phone is required'),
+ place: Yup.string().required('Address is required'),
+ city: Yup.string().required('City is required'),
+ country: Yup.string().required('Country is required'),
+ zip: Yup.number().typeError('Zip must be a number').required('Zip is required'),
+ }),
+ onSubmit: ({
+ firstName,
+ lastName,
+ email,
+ prefix,
+ number,
+ place,
+ city,
+ country,
+ zip,
+ }) => {
+ setNewUser({
+ ...newUser,
+ firstName,
+ lastName,
+ email,
+ phone: { prefix, number },
+ address: { place, city, country, zip },
+ });
+ setSelectedSection('security');
+ },
+ });
+
+ const [getCountryCodes, { loading: countryCodesLoading }] = useLazyQuery<
+ GetCountryCodesQuery,
+ GetCountryCodesQueryVariables
+ >(GET_COUNTRY_CODES, {
+ onCompleted({ getCountryCode }) {
+ setCountryCodes(getCountryCode);
+ clientCreationGeneralForm.setFieldValue(
+ 'prefix',
+ getCountryCode[0].prefix
+ );
+ clientCreationGeneralForm.setFieldValue(
+ 'country',
+ getCountryCode[0].country
+ );
+ },
+ fetchPolicy: 'network-only',
+ });
+
+ const clientCreationSecurityForm = useFormik({
+ initialValues: {
+ password: '',
+ confirmPassword: '',
+ },
+ validationSchema: Yup.object().shape({
+ password: Yup.string()
+ .required('Password is required')
+ .min(6, 'Password is 6 characters minimum'),
+ confirmPassword: Yup.string()
+ .required('Confirm password is required')
+ .oneOf(
+ [Yup.ref('password')],
+ "Confirm new password doesn't match with new password"
+ ),
+ }),
+ onSubmit: ({ password }) => {
+ setNewUser({ ...newUser, password });
+ createUser({ variables: { user: { ...newUser, password } } });
+ },
+ });
+
+ const projectMetadataForm = useFormik<{
+ frontendDevelopers: Array;
+ backendDevelopers: Array;
+ months: string;
+ summary: string;
+ purpose: string;
+ }>({
+ initialValues: {
+ frontendDevelopers: [],
+ backendDevelopers: [],
+ months: '',
+ summary: '',
+ purpose: '',
+ },
+ validationSchema: Yup.object().shape({
+ summary: Yup.string().required('Summary is required'),
+ purpose: Yup.string().required('Purpose is required'),
+ months: Yup.number().typeError('Months must be a number').required('Months is required'),
+ }),
+ onSubmit: ({
+ frontendDevelopers,
+ backendDevelopers,
+ months,
+ summary,
+ purpose,
+ }) => {
+ if (
+ !frontendDevelopers ||
+ frontendDevelopers.length === 0 ||
+ !backendDevelopers ||
+ backendDevelopers.length === 0
+ ) {
+ setError('You must select developers for your project');
+ setTimeout(() => setError(''), 3000);
+ return;
+ }
+ setProposal({
+ ...proposal,
+ devtime: {
+ months: parseInt(months, 10),
+ days: 0,
+ hours: 0,
+ },
+ resources: [
+ {
+ resourceType: 'frontend-developers',
+ developers: frontendDevelopers.length,
+ },
+ {
+ resourceType: 'backend-developers',
+ developers: backendDevelopers.length,
+ },
+ ],
+ summary,
+ purpose,
+ });
+ addProject({
+ variables: {
+ project: {
+ name: project?.name!,
+ image: {
+ name: project?.image?.name!,
+ src: project?.image?.src!,
+ },
+ features: chosenFeatures.map((feature) => feature.id)!,
+ template: chosenTemplate?.id!,
+ clientId: client?.id!,
+ platforms: chosenPlatforms,
+ delivrable: chosenDeliverables,
+ paymentOption: {
+ optOne: chosenPaymentOption?.optOne!,
+ optTwo: chosenPaymentOption?.optTwo!,
+ optThree: chosenPaymentOption?.optThree!,
+ }!,
+ totalPrice: chosenFeatures.reduce(
+ (accumulator, feature) => accumulator + feature.price,
+ 0
+ ),
+ },
+ },
+ });
+ },
+ });
+
+ const carouselSettings = {
+ pagination: false,
+ itemsToShow: 1,
+ enableSwipe: true,
+ isRTL: false,
+ disableArrowsOnEnd: true,
+ enableTilt: false,
+ renderArrow: ({
+ type,
+ onClick,
+ isEdge,
+ }: {
+ type: string;
+ onClick: () => void;
+ isEdge: boolean;
+ }) => (
+ <>
+ {type !== consts.PREV ? (
+
+ ) : (
+
+ )}
+ >
+ ),
+ transitionMs: 0,
+ };
+
+ return (
+
+
+
+
+
+ {error && }
+
+
+ }
+ onClick={() => {
+ if (step === 'categories') setStep('basic-info');
+ if (step === 'template') setStep('categories');
+ if (step === 'features') setStep('template');
+ if (step === 'deliverables-platforms') setStep('features');
+ if (step === 'payment-options')
+ setStep('deliverables-platforms');
+ if (step === 'project-metadata') setStep('client-creation');
+ }}
+ />
+
+
+ }
+ onClick={() => {
+ if (step === 'basic-info') basicInfoForm.handleSubmit();
+ if (step === 'categories') categoriesForm.handleSubmit();
+ if (step === 'template') templateForm.handleSubmit();
+ if (step === 'features') featuresForm.handleSubmit();
+ if (step === 'deliverables-platforms')
+ deliverablesPlatformsForm.handleSubmit();
+ if (step === 'payment-options')
+ paymentOptionsForm.handleSubmit();
+ if (step === 'client-creation')
+ clientCreationSecurityForm.handleSubmit();
+ if (step === 'project-metadata')
+ projectMetadataForm.handleSubmit();
+ }}
+ />
+
+
+
+ {step === 'basic-info' && (
+
+
+
+ {basicInfoForm.values.imageSource ? (
+
+
+
+ ) : (
+
+ )}
+
+
+ {basicInfoForm.values.name || 'Project Name'}
+
+
+
+
+ )}
+ {step === 'categories' && (
+ <>
+ {!categoriesLoading ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+ {step === 'template' && (
+ <>
+ {!templatesLoading ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+ {step === 'features' && (
+ <>
+ {!featuresLoading ? (
+
+
+
+ {selectedFeature && (
+ <>
+
+
+ {selectedFeature.name}
+
+
+ {selectedFeature.description}
+
+
+
+ {selectedFeature?.wireframes?.map((wireframe) => (
+
+
+
+ ))}
+
+ >
+ )}
+
+
+ ) : (
+
+ )}
+ >
+ )}
+ {step === 'deliverables-platforms' && (
+
+ )}
+ {step === 'payment-options' && (
+
+ )}
+ {step === 'client-creation' && (
+ <>
+
+
+ }
+ color={role || 'client'}
+ text='General'
+ selected={selectedSection === 'general'}
+ />
+ }
+ color={role || 'client'}
+ text='Security'
+ selected={selectedSection === 'security'}
+ />
+
+
+
+
+ {selectedSection === 'general' ? 'General' : 'Security'}
+
+ {error && }
+
+ {selectedSection === 'general' && (
+ <>
+ {!countryCodesLoading ? (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ) : (
+
+
+
+ )}
+ >
+ )}
+ {selectedSection === 'security' && (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ >
+ )}
+ {step === 'project-metadata' && (
+ <>
+ {!developersLoading ? (
+
+ ) : (
+
+ )}
+ >
+ )}
+
+
+ );
+};
+
+export default AddProject;
diff --git a/src/pages/AddProject/styles.ts b/src/pages/AddProject/styles.ts
new file mode 100644
index 0000000..986a768
--- /dev/null
+++ b/src/pages/AddProject/styles.ts
@@ -0,0 +1,28 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+export const Wrapper = styled.div`
+ .carousel-arrow {
+ background: none;
+ border: none;
+ align-self: center;
+ cursor: pointer;
+
+ svg {
+ stroke: ${({ theme, color }) => theme.colors[color || 'client'].main};
+ }
+ }
+
+ .wireframe {
+ img {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ }
+ }
+`;
diff --git a/src/pages/AddTemplate/index.tsx b/src/pages/AddTemplate/index.tsx
new file mode 100644
index 0000000..1cb24a1
--- /dev/null
+++ b/src/pages/AddTemplate/index.tsx
@@ -0,0 +1,836 @@
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+import { Navigate, useNavigate } from 'react-router';
+import {
+ useLazyQuery,
+ useMutation,
+ useQuery,
+ useReactiveVar,
+} from '@apollo/client';
+import React, { useState } from 'react';
+import { roleVar } from '../../graphql/state';
+import {
+ Box,
+ Button,
+ Text,
+ SectionSelector,
+ Input,
+ Alert,
+ TextArea,
+ Select,
+ Spinner,
+ FeatureCard,
+} from '../../components';
+import { Wrapper } from './styles';
+import { ArrowLeft, General, Specification, Features, Empty } from '../../assets';
+import {
+ AddTemplateMutation,
+ AddTemplateMutationVariables,
+ FeatureOutput,
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables,
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables,
+ TemplateInput,
+} from '../../graphql/types';
+import { ADD_TEMPLATE } from '../../graphql/template.api';
+import { GET_ALL_CATEGORIES } from '../../graphql/category.api';
+import { GET_ALL_FEATURES } from '../../graphql/feature.api';
+
+const AddTemplate = () => {
+ const navigate = useNavigate();
+ const role = useReactiveVar(roleVar);
+ const [newTemplate, setNewTemplate] = useState({
+ name: '',
+ description: '',
+ image: {
+ name: '',
+ src: '',
+ },
+ category: '',
+ specification: {
+ introduction: {
+ purpose: '',
+ documentConventions: '',
+ intendedAudience: '',
+ projectScope: '',
+ },
+ overallDescription: {
+ perspective: '',
+ userCharacteristics: '',
+ operatingEnvironment: '',
+ designImplementationConstraints: '',
+ userDocumentation: '',
+ assemptionsDependencies: '',
+ },
+ nonFunctionalRequirements: {
+ performanceRequirements: '',
+ safetyRequirements: '',
+ securityRequirements: '',
+ softwareQualityAttributes: '',
+ },
+ otherRequirements: '',
+ glossary: '',
+ analysisModels: '',
+ issuesList: '',
+ },
+ features: [],
+ });
+
+ const [availableFeatures, setAvailableFeatures] =
+ useState>();
+
+ const [selectedSection, setSelectedSection] = useState<
+ 'general' | 'specification' | 'features'
+ >('general');
+ const [error, setError] = useState('');
+
+ const { data: categories, loading: categoriesLoading, error: categoriesError } = useQuery<
+ GetAllCategoriesQuery,
+ GetAllCategoriesQueryVariables
+ >(GET_ALL_CATEGORIES, {
+ fetchPolicy: 'network-only',
+ });
+
+ const [getFeatures, { loading: featuresLoading, error: featuresError }] = useLazyQuery<
+ GetAllFeaturesQuery,
+ GetAllFeaturesQueryVariables
+ >(GET_ALL_FEATURES, {
+ onCompleted({ getAllFeatures }) {
+ setAvailableFeatures(getAllFeatures);
+ },
+ fetchPolicy: 'network-only',
+ });
+
+ const [addTemplate, { loading }] = useMutation<
+ AddTemplateMutation,
+ AddTemplateMutationVariables
+ >(ADD_TEMPLATE, {
+ onCompleted({ addTemplate: { id } }) {
+ navigate(`/template/${id}`);
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const generalForm = useFormik({
+ initialValues: {
+ name: '',
+ description: '',
+ imageName: '',
+ imageSource: '',
+ category: '',
+ },
+ validationSchema: Yup.object().shape({
+ name: Yup.string().required('Name is required'),
+ description: Yup.string().required('Description is required'),
+ imageName: Yup.string().required('Image is required'),
+ imageSource: Yup.string().required('Image is required'),
+ category: Yup.string().required('Category is required'),
+ }),
+ onSubmit: ({ name, description, category, imageName, imageSource }) => {
+ setNewTemplate({
+ name,
+ description,
+ image: { name: imageName, src: imageSource },
+ category,
+ });
+ setSelectedSection('specification');
+ },
+ });
+
+ const specificationForm = useFormik({
+ initialValues: {
+ purpose: '',
+ documentConventions: '',
+ intendedAudience: '',
+ projectScope: '',
+ perspective: '',
+ userCharacteristics: '',
+ operatingEnvironment: '',
+ designImplementationConstraints: '',
+ userDocumentation: '',
+ assemptionsDependencies: '',
+ performanceRequirements: '',
+ safetyRequirements: '',
+ securityRequirements: '',
+ softwareQualityAttributes: '',
+ otherRequirements: '',
+ glossary: '',
+ analysisModels: '',
+ issuesList: '',
+ },
+ validationSchema: Yup.object().shape({
+ purpose: Yup.string().required('Purpose is required'),
+ documentConventions: Yup.string().required(
+ 'Document conventions is required'
+ ),
+ intendedAudience: Yup.string().required('Intented audience is required'),
+ projectScope: Yup.string().required('Project scope is required'),
+ perspective: Yup.string().required('Perspective is required'),
+ userCharacteristics: Yup.string().required(
+ 'User characteristics is required'
+ ),
+ operatingEnvironment: Yup.string().required(
+ 'Operating environment is required'
+ ),
+ designImplementationConstraints: Yup.string().required(
+ 'Design and implementation constraints is required'
+ ),
+ userDocumentation: Yup.string().required(
+ 'User documentation is required'
+ ),
+ assemptionsDependencies: Yup.string().required(
+ 'Assumptions and dependencies is required'
+ ),
+ performanceRequirements: Yup.string().required(
+ 'Performance requirements is required'
+ ),
+ safetyRequirements: Yup.string().required(
+ 'Safety requirements is required'
+ ),
+ securityRequirements: Yup.string().required(
+ 'Security requirements is required'
+ ),
+ softwareQualityAttributes: Yup.string().required(
+ 'Software quality attributes is required'
+ ),
+ otherRequirements: Yup.string().required(
+ 'Other requirements is required'
+ ),
+ glossary: Yup.string().required('Glossary is required'),
+ analysisModels: Yup.string().required('Analysis models is required'),
+ issuesList: Yup.string().required('Issues list is required'),
+ }),
+ onSubmit: ({
+ purpose,
+ documentConventions,
+ intendedAudience,
+ projectScope,
+ perspective,
+ userCharacteristics,
+ operatingEnvironment,
+ designImplementationConstraints,
+ userDocumentation,
+ assemptionsDependencies,
+ performanceRequirements,
+ safetyRequirements,
+ securityRequirements,
+ softwareQualityAttributes,
+ otherRequirements,
+ glossary,
+ analysisModels,
+ issuesList,
+ }) => {
+ setNewTemplate({
+ ...newTemplate,
+ specification: {
+ introduction: {
+ purpose,
+ documentConventions,
+ intendedAudience,
+ projectScope,
+ },
+ overallDescription: {
+ perspective,
+ userCharacteristics,
+ operatingEnvironment,
+ designImplementationConstraints,
+ userDocumentation,
+ assemptionsDependencies,
+ },
+ nonFunctionalRequirements: {
+ performanceRequirements,
+ safetyRequirements,
+ securityRequirements,
+ softwareQualityAttributes,
+ },
+ otherRequirements,
+ glossary,
+ analysisModels,
+ issuesList,
+ },
+ });
+ setSelectedSection('features');
+ getFeatures();
+ },
+ });
+
+ const featuresForm = useFormik<{ features: Array }>({
+ initialValues: {
+ features: [],
+ },
+ onSubmit: ({ features }) => {
+ addTemplate({ variables: { template: { ...newTemplate, features } } });
+ },
+ });
+
+ if (role !== 'productOwner') return (
+ <>
+ {role === 'admin' && }
+ {['client', 'developer'].includes(role as string) && }
+ >
+ );
+
+ if (categoriesLoading || featuresLoading) return (
+
+ );
+
+ if (categoriesError || featuresError || !categories) return (
+
+
+
+
+
+
+
+ );
+
+ return (
+
+
+
+
+
+ }
+ color={role || 'client'}
+ text='General'
+ selected={selectedSection === 'general'}
+ />
+ }
+ color={role || 'client'}
+ text='Specification'
+ selected={selectedSection === 'specification'}
+ />
+ }
+ color={role || 'client'}
+ text='Features'
+ selected={selectedSection === 'features'}
+ />
+
+
+ {selectedSection === 'general' && (
+ <>
+ {!categoriesLoading ? (
+ <>
+
+
+ {selectedSection === 'general' ? 'General' : 'Wireframes'}
+
+ {error && }
+
+
+ >
+ ) : (
+
+
+
+ )}
+ >
+ )}
+ {selectedSection === 'specification' && (
+ <>
+
+
+ Specification
+
+ {error && }
+
+
+ >
+ )}
+ {selectedSection === 'features' && (
+ <>
+
+
+ Features
+
+ {error && }
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default AddTemplate;
diff --git a/src/pages/AddTemplate/styles.ts b/src/pages/AddTemplate/styles.ts
new file mode 100644
index 0000000..35a77fc
--- /dev/null
+++ b/src/pages/AddTemplate/styles.ts
@@ -0,0 +1,14 @@
+import styled from 'styled-components';
+
+type WrapperProps = {
+ color?: 'client' | 'productOwner' | 'developer' | 'admin';
+};
+
+export const Wrapper = styled.div`
+ padding: 35px 45px 35px 120px;
+
+ .empty {
+ fill: ${({ theme, color }) =>
+ color ? theme.colors[color].main : theme.colors.client.main};
+ }
+`;
diff --git a/src/pages/Auth/AdditionalInfo/index.tsx b/src/pages/Auth/AdditionalInfo/index.tsx
new file mode 100644
index 0000000..4254418
--- /dev/null
+++ b/src/pages/Auth/AdditionalInfo/index.tsx
@@ -0,0 +1,279 @@
+import * as Yup from 'yup';
+import { useFormik } from 'formik';
+import { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import { useMutation, useQuery, useReactiveVar } from '@apollo/client';
+import {
+ Box,
+ Button,
+ Input,
+ Select,
+ Text,
+ Alert,
+ Spinner,
+} from '../../../components';
+import { theme } from '../../../themes';
+import { Wrapper } from './styles';
+import {
+ GetCountryCodesQuery,
+ GetCountryCodesQueryVariables,
+ UpdateUserInfoMutation,
+ UpdateUserInfoMutationVariables,
+} from '../../../graphql/types';
+import { GET_COUNTRY_CODES, UPDATE_USER_INFO } from '../../../graphql/auth.api';
+import { userVar } from '../../../graphql/state';
+
+const AdditionalInfo = () => {
+ const navigate = useNavigate();
+ const [error, setError] = useState('');
+ const currentUser = useReactiveVar(userVar);
+ const { data: countryCodes, loading: countryCodesLoading } = useQuery<
+ GetCountryCodesQuery,
+ GetCountryCodesQueryVariables
+ >(GET_COUNTRY_CODES);
+
+ const [updateUserInfo, { loading }] = useMutation<
+ UpdateUserInfoMutation,
+ UpdateUserInfoMutationVariables
+ >(UPDATE_USER_INFO, {
+ onCompleted({ updateUserInfo: user }) {
+ userVar(user);
+ navigate('/');
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const form = useFormik({
+ initialValues: {
+ firstName: '',
+ lastName: '',
+ prefix: '',
+ number: '',
+ place: '',
+ city: '',
+ zip: '',
+ country: '',
+ },
+ validationSchema: Yup.object().shape({
+ firstName: Yup.string().required('First Name is required'),
+ lastName: Yup.string().required('Last Name is required'),
+ prefix: Yup.string().required('Prefix is required'),
+ number: Yup.number()
+ // prettier-ignore
+ .typeError('Phone must be a number')
+ .required('Phone is required'),
+ place: Yup.string().required('Address is required'),
+ city: Yup.string().required('City is required'),
+ country: Yup.string().required('Country is required'),
+ zip: Yup.number()
+ // prettier-ignore
+ .typeError('Zip must be a number')
+ .required('Zip is required'),
+ }),
+ onSubmit: ({
+ firstName,
+ lastName,
+ prefix,
+ number,
+ place,
+ city,
+ country,
+ zip,
+ }) =>
+ updateUserInfo({
+ variables: {
+ user: {
+ id: currentUser?.id!,
+ email: currentUser?.email!,
+ firstName,
+ lastName,
+ phone: { prefix, number },
+ address: { place, city, country, zip },
+ role: currentUser?.role!,
+ },
+ },
+ }),
+ });
+
+ return (
+
+
+
+
+
+ Tell us more about yourself
+
+
+ {!countryCodesLoading ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
+};
+
+export default AdditionalInfo;
diff --git a/src/pages/Auth/AdditionalInfo/styles.ts b/src/pages/Auth/AdditionalInfo/styles.ts
new file mode 100644
index 0000000..cd082fe
--- /dev/null
+++ b/src/pages/Auth/AdditionalInfo/styles.ts
@@ -0,0 +1,5 @@
+import styled from 'styled-components';
+
+export const Wrapper = styled.div`
+ background: ${({ theme }) => theme.colors.client.main};
+`;
diff --git a/src/pages/Auth/ForgotPassword/index.tsx b/src/pages/Auth/ForgotPassword/index.tsx
new file mode 100644
index 0000000..b468920
--- /dev/null
+++ b/src/pages/Auth/ForgotPassword/index.tsx
@@ -0,0 +1,161 @@
+import * as Yup from 'yup';
+import { useMutation } from '@apollo/client';
+import { useFormik } from 'formik';
+import { useState } from 'react';
+import { Login as LoginIllustration, Logo } from '../../../assets';
+import { Box, Button, Input, Link, Text, Alert } from '../../../components';
+import { RESET_PASSWORD } from '../../../graphql/auth.api';
+import {
+ ResetPasswordMutation,
+ ResetPasswordMutationVariables,
+} from '../../../graphql/types';
+import { theme } from '../../../themes';
+import { Wrapper } from './styles';
+
+const ForgotPassword = () => {
+ const [error, setError] = useState('');
+ const [success, setSuccess] = useState(false);
+
+ const [resetPassword, { loading }] = useMutation<
+ ResetPasswordMutation,
+ ResetPasswordMutationVariables
+ >(RESET_PASSWORD, {
+ onCompleted() {
+ setSuccess(true);
+ setTimeout(() => setSuccess(false), 3000);
+ },
+ onError({ graphQLErrors }) {
+ setError(graphQLErrors[0]?.extensions?.info as string);
+ setTimeout(() => setError(''), 3000);
+ },
+ });
+
+ const form = useFormik({
+ initialValues: {
+ email: '',
+ password: '',
+ },
+ validationSchema: Yup.object().shape({
+ email: Yup.string()
+ .required('Email is required')
+ .email('Email is invalid'),
+ }),
+ onSubmit: ({ email }) => {
+ resetPassword({ variables: { email } });
+ },
+ });
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ Forgot Password
+
+ {error && }
+ {success && (
+
+ )}
+
+