├── .editorconfig ├── .enum_manifest.json ├── .eslintrc ├── .gitignore ├── .nvmrc ├── .source_strings.json ├── .src_manifest.json ├── .storybook └── config.js ├── LICENSE.md ├── README.md ├── cypress.json ├── cypress ├── fixtures │ ├── example.json │ ├── profile.json │ └── users.json ├── integration │ ├── client_spec.js │ ├── dashboard_spec.js │ ├── onboarding_spec.js │ ├── signin_spec.js │ └── signup_spec.js ├── plugins │ └── index.js ├── screenshots │ └── client_spec.js │ │ └── Client -- should allow to create a client (failed).png ├── support │ ├── commands.js │ └── index.js └── videos │ ├── client_spec.js-compressed.mp4 │ └── client_spec.js.mp4 ├── fetchFragment.js ├── package.json ├── public ├── _redirects ├── community-inyo.png ├── favicon │ ├── android-icon-144x144.png │ ├── android-icon-192x192.png │ ├── android-icon-36x36.png │ ├── android-icon-48x48.png │ ├── android-icon-72x72.png │ ├── android-icon-96x96.png │ ├── apple-icon-114x114.png │ ├── apple-icon-120x120.png │ ├── apple-icon-144x144.png │ ├── apple-icon-152x152.png │ ├── apple-icon-180x180.png │ ├── apple-icon-57x57.png │ ├── apple-icon-60x60.png │ ├── apple-icon-72x72.png │ ├── apple-icon-76x76.png │ ├── apple-icon-precomposed.png │ ├── apple-icon.png │ ├── browserconfig.xml │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── favicon.ico │ ├── ms-icon-144x144.png │ ├── ms-icon-150x150.png │ ├── ms-icon-310x310.png │ └── ms-icon-70x70.png ├── fonts │ ├── Ligne-Bold.otf │ ├── Ligne-Medium.otf │ └── Ligne-SemiBold.otf ├── index.html └── manifest.json ├── sentry-release-script ├── src ├── HOC │ ├── withHeader.js │ └── withTracker.js ├── Storyshots.test.js ├── __snapshots__ │ └── Storyshots.test.js.snap ├── components │ ├── ActivityFeed │ │ └── index.js │ ├── AddCollaboratorModal │ │ └── index.js │ ├── AddressAutocomplete │ │ └── index.js │ ├── Apostrophe │ │ └── index.js │ ├── ArianneThread │ │ └── index.js │ ├── AssignedToOtherCard │ │ └── index.js │ ├── AssistantActions │ │ └── index.js │ ├── BistableButton │ │ └── index.js │ ├── CheckList │ │ └── index.js │ ├── CollabLinkToProjectList │ │ └── index.js │ ├── CollaboratorAvatar │ │ └── index.js │ ├── CollaboratorDropdown │ │ └── index.js │ ├── CollaboratorList │ │ └── index.js │ ├── CollaboratorModal │ │ └── index.js │ ├── Comment │ │ └── index.js │ ├── CommentList │ │ └── index.js │ ├── ConfirmFinishCustomerTaskModal │ │ └── index.js │ ├── ConfirmModal │ │ └── index.js │ ├── CreateProjectLinkButton │ │ └── index.js │ ├── CreateProjectModal │ │ └── index.js │ ├── CreateProjectModalForm │ │ └── index.js │ ├── CreateProjectModalViewContent │ │ └── index.js │ ├── CreateTask │ │ └── index.js │ ├── CustomEmailSidebar │ │ └── index.js │ ├── CustomEmailSidebarCategory │ │ └── index.js │ ├── CustomEmailTimingInput │ │ └── index.js │ ├── CustomProjectHeader │ │ └── index.js │ ├── CustomerDropdown │ │ └── index.js │ ├── CustomerIntroMail │ │ └── index.js │ ├── CustomerModal │ │ └── index.js │ ├── CustomerModalAndMail │ │ └── index.js │ ├── CustomerNameAndAddress │ │ └── index.js │ ├── CustomerTaskRow │ │ └── index.js │ ├── CustomersDropdown │ │ └── index.js │ ├── DateInput │ │ └── index.js │ ├── DeadlineCard │ │ └── index.js │ ├── DefaultDroppableDay │ │ └── index.js │ ├── DoubleRangeTimeInput │ │ └── index.js │ ├── DuplicateProjectButton │ │ └── index.js │ ├── EmailCustomizer │ │ └── index.js │ ├── EmailExample │ │ └── index.js │ ├── EmailParam │ │ └── index.js │ ├── EmailParamList │ │ └── index.js │ ├── FormCheckbox │ │ └── index.js │ ├── FormElem │ │ └── index.js │ ├── FormInput │ │ └── index.js │ ├── FormSelect │ │ └── index.js │ ├── FormTextarea │ │ └── index.js │ ├── HelpAndTooltip │ │ └── index.js │ ├── HelpButton │ │ └── index.js │ ├── HelpModal │ │ └── index.js │ ├── IconButton │ │ └── index.js │ ├── ImagePickerModal │ │ └── index.js │ ├── InitialIdentifier │ │ └── index.js │ ├── InlineEditable │ │ └── index.js │ ├── IssuerNameAndAddress │ │ └── index.js │ ├── ItemView │ │ └── index.js │ ├── ItemViewAssigneeInput │ │ └── index.js │ ├── LeftBarSchedule │ │ └── index.js │ ├── Legend │ │ └── index.js │ ├── LinkedCalendarForm │ │ └── index.js │ ├── LoginForm │ │ └── index.js │ ├── MaterialIcon │ │ ├── index.css │ │ └── index.js │ ├── ModalWithHoursAndDays │ │ └── index.js │ ├── MultilineEditable │ │ └── index.js │ ├── NoticeModal │ │ └── index.js │ ├── NotificationItem │ │ └── index.js │ ├── NotificationTrayButton │ │ └── index.js │ ├── Onboarding │ │ ├── onboarding-calendar.js │ │ ├── onboarding-custom-assistant.js │ │ ├── onboarding-first-step.js │ │ ├── onboarding-skills.js │ │ └── onboarding-third-step.js │ ├── PendingActionsTray │ │ └── index.js │ ├── PieChart │ │ └── index.js │ ├── PopinTask │ │ └── index.js │ ├── ProjectBudget │ │ └── index.js │ ├── ProjectCollaboratorsDropdown │ │ └── index.js │ ├── ProjectCustomerTasksList │ │ └── index.js │ ├── ProjectData │ │ └── index.js │ ├── ProjectDocumentsFolders │ │ └── index.js │ ├── ProjectHeader │ │ └── index.js │ ├── ProjectInput │ │ └── index.js │ ├── ProjectNotes │ │ └── index.js │ ├── ProjectPersonalNotes │ │ └── index.js │ ├── ProjectQuotes │ │ └── index.js │ ├── ProjectSection │ │ └── index.js │ ├── ProjectSharedNotes │ │ └── index.js │ ├── ProjectTasksList │ │ └── index.js │ ├── ProjectsDropdown │ │ └── index.js │ ├── ReminderCard │ │ └── index.js │ ├── ReminderTestEmailButton │ │ └── index.js │ ├── RemoveProjectButton │ │ └── index.js │ ├── RemoveProjectModal │ │ └── index.js │ ├── RescheduleModal │ │ └── index.js │ ├── ResetPasswordForm │ │ └── index.js │ ├── RichTextEditor │ │ └── index.js │ ├── Schedule │ │ └── index.js │ ├── SendResetPasswordForm │ │ └── index.js │ ├── SettingsForm │ │ └── index.js │ ├── SidebarCustomerProjectInfos │ │ └── index.js │ ├── SidebarDashboardInfos │ │ └── index.js │ ├── SidebarProjectInfos │ │ └── index.js │ ├── SignupForm │ │ └── index.js │ ├── StaticCustomerView │ │ └── index.js │ ├── SwitchButton │ │ └── index.js │ ├── Tag │ │ └── index.js │ ├── TagDropdown │ │ └── index.js │ ├── TagForm │ │ └── index.js │ ├── TagList │ │ └── index.js │ ├── TagListForm │ │ └── index.js │ ├── TaskActivationButton │ │ └── index.js │ ├── TaskActivationHeader │ │ └── index.js │ ├── TaskActivationModal │ │ └── index.js │ ├── TaskCard │ │ └── index.js │ ├── TaskCollaboratorList │ │ └── index.js │ ├── TaskComment │ │ └── index.js │ ├── TaskCustomerActivationButton │ │ └── index.js │ ├── TaskCustomerInput │ │ └── index.js │ ├── TaskDescription │ │ └── index.js │ ├── TaskDueDate │ │ └── index.js │ ├── TaskInfosInputs │ │ └── index.js │ ├── TaskInput │ │ ├── index.js │ │ └── index.stories.js │ ├── TaskReminderIcon │ │ └── index.js │ ├── TaskRemindersList │ │ └── index.js │ ├── TaskRemindersPreviewsList │ │ └── index.js │ ├── TaskRow │ │ └── index.js │ ├── TaskStatusButton │ │ └── index.js │ ├── TaskTypeDropdown │ │ └── index.js │ ├── TaskUnitInfo │ │ └── index.js │ ├── TasksList │ │ └── index.js │ ├── TasksProgressBar │ │ └── index.js │ ├── TemplateAndProjectFiller │ │ └── index.js │ ├── TimeItTookDisplay │ │ └── index.js │ ├── TimingInput │ │ └── index.js │ ├── Tooltip │ │ └── index.js │ ├── TopBar │ │ └── index.js │ ├── TrialHeadband │ │ └── index.js │ ├── UnitDisplay │ │ └── index.js │ ├── UnitInput │ │ └── index.js │ ├── UnitWithSuggestionsForm │ │ └── index.js │ ├── UploadDashboardButton │ │ └── index.js │ ├── UserAssistantForm │ │ └── index.js │ ├── UserCompanyForm │ │ └── index.js │ ├── UserDataForm │ │ └── index.js │ ├── WeekDaysInput │ │ └── index.js │ └── WelcomeModal │ │ └── index.js ├── dragdroptouch.js ├── fbt │ ├── fbt.macro.js │ └── generateTranslateFile.js ├── index.css ├── index.js ├── print.css ├── providers │ └── sentry.js ├── screens │ ├── App │ │ ├── Account │ │ │ └── index.js │ │ ├── Collaborators │ │ │ └── index.js │ │ ├── ConditionalContent │ │ │ └── index.js │ │ ├── Customers │ │ │ └── index.js │ │ ├── CustomizeEmail │ │ │ └── index.js │ │ ├── Dashboard │ │ │ ├── index.js │ │ │ └── tasks.js │ │ ├── Onboarding │ │ │ └── index.js │ │ ├── Projects │ │ │ └── index.js │ │ ├── Stats │ │ │ └── index.js │ │ ├── Tags │ │ │ └── index.js │ │ ├── Tasks │ │ │ ├── index.js │ │ │ └── tasks-lists.js │ │ └── index.js │ ├── Auth │ │ └── index.js │ ├── Customer │ │ ├── Quote │ │ │ └── index.js │ │ ├── Tasks │ │ │ ├── index.js │ │ │ └── tasks.js │ │ └── index.js │ ├── EndOfTrial │ │ └── index.js │ ├── Paid │ │ └── index.js │ ├── PrematureEndOfTrial │ │ └── index.js │ └── StraightToCheckout │ │ └── index.js ├── serviceWorker.js ├── translatedFbts.json ├── translatedFbts.json.rej └── utils │ ├── accountSync │ ├── appleAccount.js │ └── googleAccount.js │ ├── apollo-hooks.js │ ├── calendars │ ├── appleCalendar.js │ └── googleCalendar.js │ ├── colors.js │ ├── constants.js │ ├── content.js │ ├── contexts.js │ ├── fonts │ └── WorkSans-Italic.otf │ ├── fragmentTypes.json │ ├── fragments.js │ ├── functions.js │ ├── graphQLConfig.js │ ├── icons │ ├── appLogo.svg │ ├── content-acquisition.svg │ ├── drag.svg │ ├── invoice-icon.svg │ ├── inyo-topbar-logo.svg │ ├── pencil.svg │ ├── search.svg │ ├── section-icon.svg │ ├── tags.svg │ ├── taskicon-collaborator-validated-anim.svg │ ├── taskicon-collaborator-validated.svg │ ├── taskicon-collaborator.svg │ ├── taskicon-customer-validated-anim.svg │ ├── taskicon-customer-validated.svg │ ├── taskicon-customer.svg │ ├── taskicon-personal.svg │ ├── taskicon-user-validated-anim.svg │ ├── taskicon-user-validated.svg │ └── taskicon.svg │ ├── images │ ├── bermuda-calendar.svg │ ├── bermuda-coming-soon.svg │ ├── bermuda-delete-confirmation.svg │ ├── bermuda-done.svg │ ├── bermuda-fatal-error.svg │ ├── bermuda-hello-edwige.svg │ ├── bermuda-logged-out.svg │ ├── bermuda-no-comments.svg │ ├── bermuda-no-message.svg │ ├── bermuda-order-completed.svg │ ├── bermuda-page-not-found.svg │ ├── bermuda-searching.svg │ ├── bermuda-success.svg │ ├── bermuda-uploading.svg │ ├── bermuda-welcome.svg │ ├── empty-clients-background.svg │ ├── empty-clients-illus.svg │ ├── empty-project-background.svg │ ├── empty-project-illus.svg │ ├── empty-tasks-background.svg │ ├── empty-tasks-illus.svg │ └── google_g_logo.svg │ ├── mutationLinks │ ├── acceptCollabRequest.js │ ├── createCustomer.js │ ├── createProject.js │ ├── createSection.js │ ├── createTag.js │ ├── createTask.js │ ├── deleteTask.js │ ├── finishItem.js │ ├── focusTask.js │ ├── issueQuote.js │ ├── removeAttachment.js │ ├── removeCustomer.js │ ├── removeProject.js │ ├── removeSection.js │ ├── removeTag.js │ ├── requestCollab.js │ ├── startTaskTimer.js │ ├── stopCurrentTaskTimer.js │ ├── unfinishItem.js │ ├── unfocusTask.js │ ├── updateItem.js │ ├── updateProject.js │ ├── updateSection.js │ ├── updateUser.js │ └── uploadAttachments.js │ ├── mutations.js │ ├── new │ └── design-system.js │ ├── project-templates.js │ ├── queries.js │ ├── reorderList.js │ ├── timezone-data.js │ ├── timezones.js │ ├── useAccount.js │ ├── useApolloQuery.js │ ├── useCalendar.js │ ├── useEmailData.js │ ├── useLocalStorage.js │ ├── useMeasure.js │ ├── useOnClickOutside.js │ ├── usePrevious.js │ ├── useScheduleData.jsx │ └── useUserInfos.js ├── translations └── en-US.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | # Change these settings to your own preference 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | # We recommend you to keep these unchanged 14 | end_of_line = lf 15 | charset = utf-8 16 | trim_trailing_whitespace = true 17 | insert_final_newline = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.enum_manifest.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base"], 3 | "env": { 4 | "es6": true, 5 | "browser": true, 6 | "cypress/globals": true 7 | }, 8 | "parser": "babel-eslint", 9 | "parserOptions": { 10 | "ecmaVersion": 2017, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true, 14 | "experimentalObjectRestSpread": true 15 | } 16 | }, 17 | "rules": { 18 | "strict": 0, 19 | "no-constant-condition": 2, 20 | "no-extra-parens": [2, "functions"], 21 | "arrow-parens": [2, "as-needed", {"requireForBlockBody": true}], 22 | "no-inner-declarations": [2, "functions"], 23 | "no-negated-in-lhs": 1, 24 | "no-unused-expressions": [ 25 | 2, 26 | {"allowShortCircuit": true, "allowTernary": true} 27 | ], 28 | "no-useless-call": 2, 29 | "vars-on-top": 1, 30 | "no-catch-shadow": 2, 31 | "no-shadow-restricted-names": 2, 32 | "no-shadow": 2, 33 | "no-undef-init": 2, 34 | "no-undef": 2, 35 | "no-unused-vars": 1, 36 | "no-use-before-define": [2, "nofunc"], 37 | "brace-style": [2, "stroustrup", {"allowSingleLine": true}], 38 | "consistent-this": [2, "self"], 39 | "max-depth": [1, 3], 40 | "no-mixed-spaces-and-tabs": [2, "smart-tabs"], 41 | "no-negated-condition": 1, 42 | "object-curly-spacing": [2, "never"], 43 | "operator-linebreak": [2, "before"], 44 | "semi": 1, 45 | "keyword-spacing": [2, {"before": true, "after": true}], 46 | "space-infix-ops": [2, {"int32Hint": false}], 47 | "class-methods-use-this": 0, 48 | "block-spacing": ["error", "never"], 49 | "no-plusplus": 0, 50 | "no-mixed-operators": ["error", {"allowSamePrecedence": true}], 51 | 52 | "max-len": "off", 53 | "indent": ["error", "tab"], 54 | "react/jsx-indent": "off", 55 | "react/jsx-indent-props": "off", 56 | "no-tabs": 0, 57 | 58 | "react/prop-types": 0, 59 | "react/require-default-props": 0, 60 | "react/no-unescaped-entities": 0, 61 | "react/jsx-uses-react": "error", 62 | "react/jsx-uses-vars": "error", 63 | 64 | "import/extensions": 1, 65 | 66 | "sort-imports": "off", 67 | "import/order": "off", 68 | "simple-import-sort/sort": "error" 69 | }, 70 | "plugins": ["react", "prettier", "cypress", "simple-import-sort"], 71 | "settings": { 72 | "import/resolver": { 73 | "node": { 74 | "extensions": [".js", ".jsx"] 75 | } 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # vim 4 | *.sw* 5 | .tern-project 6 | .sentryclirc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # testing 12 | /coverage 13 | 14 | # production 15 | /build 16 | 17 | # misc 18 | .DS_Store 19 | .env.local 20 | .env.development.local 21 | .env.test.local 22 | .env.production.local 23 | .envrc 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | package-lock.json 29 | 30 | # ctags 31 | tags.* 32 | tags 33 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.20.1 2 | -------------------------------------------------------------------------------- /.storybook/config.js: -------------------------------------------------------------------------------- 1 | import requireContext from 'require-context.macro'; 2 | import { configure } from '@storybook/react'; 3 | 4 | function loadStories() { 5 | const req = requireContext('../src', true, /\.stories\.js$/); 6 | req.keys().forEach(filename => req(filename)); 7 | } 8 | 9 | configure(loadStories, module); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 | --- 6 | 7 |

8 | Smart Project Manager for freelancers. 9 |

10 |

11 | 12 | Netlify Status 13 | 14 |

15 | 16 | ## Installation & Running 17 | 18 | You can use either Yarn or NPM. 19 | 20 | ```bash 21 | yarn install 22 | yarn start 23 | ``` 24 | 25 | If you want to run your own backend, you need to setup the [Inyo API](https://github.com/byte-foundry/inyo-api) first and configure the following variable: 26 | 27 | ```bash 28 | export REACT_APP_GRAPHQL_API=http://localhost:3000 29 | ``` 30 | 31 | ## Translating 32 | 33 | ```bash 34 | # Collect all files containing the translation tags 35 | yarn manifest 36 | 37 | # Collect translations from the files and generate source 38 | yarn collect-fbts 39 | 40 | # Update en-US translation file 41 | yarn generate-locale-file 42 | ``` 43 | Then, translate all lines marked with `#TODO` in the translation file in the `translations` folder. 44 | ```bash 45 | # Generate locale file for the app 46 | yarn translate-fbts 47 | ``` 48 | 49 | ## Contributing 50 | Pull requests are welcome. If you find a bug, please check there is not already an issue opened about it. 51 | 52 | ## License 53 | [AGPLv3](https://github.com/byte-foundry/inyo/blob/release/LICENSE.md) 54 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "projectId": "pdp1pu", 3 | "baseUrl": "http://localhost:3000" 4 | } 5 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } -------------------------------------------------------------------------------- /cypress/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 8739, 3 | "name": "Jane", 4 | "email": "jane@example.com" 5 | } -------------------------------------------------------------------------------- /cypress/integration/dashboard_spec.js: -------------------------------------------------------------------------------- 1 | describe('Client', () => { 2 | before(() => { 3 | cy.visit('/auth/sign-in'); 4 | 5 | cy.get('input[name=email]') 6 | .type('notused@used.email') 7 | .should('have.value', 'notused@used.email') 8 | .blur(); 9 | 10 | cy.get('input[name=password]') 11 | .type('testtest') 12 | .should('have.value', 'testtest') 13 | .blur(); 14 | 15 | cy.contains('Se connecter').click(); 16 | 17 | cy.url().should('include', 'app/tasks'); 18 | 19 | cy.contains('Dashboard').click(); 20 | 21 | cy.url().should('include', 'app/dashboard'); 22 | }); 23 | 24 | it('should be able to drop a task from the list', () => { 25 | cy.get('.css-101ikum').type('Tâche 1{enter}'); 26 | 27 | cy.contains('.css-136buib-TaskContainer', 'Tâche 1').drag( 28 | '.css-n8vpxk > :nth-child(1) > .css-1j4qrhy > .css-ti9njt', 29 | ); 30 | 31 | cy.contains('.css-n8vpxk > :nth-child(1) > .css-1j4qrhy', 'Tâche 1'); 32 | }); 33 | 34 | // don't know why this doesn't work 35 | 36 | // it('should be able to drop a task from the first day to the second', () => { 37 | // cy.contains('.css-n8vpxk > :nth-child(1)', 'Tâche 1') 38 | // .drag('.css-n8vpxk > :nth-child(2) > .css-1j4qrhy > .css-ti9njt', 'center'); 39 | 40 | // cy.contains('.css-n8vpxk > :nth-child(2) > .css-1j4qrhy', 'Tâche 1') 41 | // }); 42 | 43 | // it('should be able to drop a task from the first day to the list', () => { 44 | // cy.contains('.css-n8vpxk > :nth-child(1)', 'Tâche 1') 45 | // .drag('.css-1bemi39-TasksListContainer', 'center'); 46 | // }); 47 | 48 | it('should be able to remove a task from the schedule', () => { 49 | cy.get('.css-n8vpxk > :nth-child(1) [draggable]').drag('.css-6mxnnv'); 50 | 51 | cy.contains('.css-136buib-TaskContainer', 'Tâche 1'); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /cypress/integration/signin_spec.js: -------------------------------------------------------------------------------- 1 | describe('Sign in', () => { 2 | it('should require an email and a password', () => { 3 | cy.visit('/'); 4 | 5 | cy.url().should('include', '/auth/sign-up'); 6 | 7 | cy.contains('Se connecter').click(); 8 | 9 | cy.url().should('include', '/auth/sign-in'); 10 | 11 | cy.contains('Se connecter').click(); 12 | 13 | cy.get('.input-feedback').should('have.length', 2); 14 | }); 15 | 16 | it('should require a valid email', () => { 17 | cy.visit('/auth/sign-in'); 18 | 19 | cy.get('input[name=email]') 20 | .type('a') 21 | .should('have.value', 'a') 22 | .blur(); 23 | 24 | cy.contains("email n'est pas valide"); 25 | }); 26 | 27 | it('should require a valid account', () => { 28 | cy.visit('/auth/sign-in'); 29 | 30 | cy.get('input[name=email]') 31 | .type('doesnot@exist.account') 32 | .should('have.value', 'doesnot@exist.account') 33 | .blur(); 34 | 35 | cy.get('input[name=password]') 36 | .type('testtest') 37 | .should('have.value', 'testtest') 38 | .blur(); 39 | 40 | cy.contains('Se connecter').click(); 41 | 42 | cy.contains('Mauvais email ou mot de passe'); 43 | }); 44 | 45 | it('should require a valid passord', () => { 46 | cy.visit('/auth/sign-in'); 47 | 48 | cy.get('input[name=email]') 49 | .type('notused@used.email') 50 | .should('have.value', 'notused@used.email') 51 | .blur(); 52 | 53 | cy.get('input[name=password]') 54 | .type('testtest1') 55 | .should('have.value', 'testtest1') 56 | .blur(); 57 | 58 | cy.contains('Se connecter').click(); 59 | 60 | cy.contains('Mauvais email ou mot de passe'); 61 | }); 62 | 63 | it('should allow user to sign in ', () => { 64 | cy.visit('/auth/sign-in'); 65 | 66 | cy.get('input[name=email]') 67 | .type('notused@used.email') 68 | .should('have.value', 'notused@used.email') 69 | .blur(); 70 | 71 | cy.get('input[name=password]') 72 | .type('testtest') 73 | .should('have.value', 'testtest') 74 | .blur(); 75 | 76 | cy.contains('Se connecter').click(); 77 | 78 | cy.url().should('include', '/app/dashboard'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /cypress/integration/signup_spec.js: -------------------------------------------------------------------------------- 1 | describe('Sign up', () => { 2 | beforeEach(() => { 3 | cy.visit('/'); 4 | 5 | cy.url().should('include', '/auth/sign-up'); 6 | }); 7 | 8 | it('should require an email, a password, a last name and a firstname', () => { 9 | cy.get('#toscheck').click(); 10 | cy.contains('Commencez').click(); 11 | 12 | cy.get('.input-feedback').should('have.length', 4); 13 | }); 14 | 15 | it('should not allow an invalid email', () => { 16 | cy.get('#toscheck').click(); 17 | cy.get('input[name=email]') 18 | .type('already@used') 19 | .should('have.value', 'already@used') 20 | .blur(); 21 | 22 | cy.contains('email doit être valide'); 23 | }); 24 | 25 | it('should not allow a already used email', () => { 26 | cy.get('#toscheck').click(); 27 | cy.get('input[name=email]') 28 | .type('already@used.email') 29 | .should('have.value', 'already@used.email') 30 | .blur(); 31 | 32 | cy.contains('email est déjà utilisé'); 33 | }); 34 | 35 | it('should allow to sign up', () => { 36 | cy.get('#toscheck').click(); 37 | cy.get('input[name=email]') 38 | .type('notused@used.email') 39 | .should('have.value', 'notused@used.email'); 40 | 41 | cy.get('input[name=password]') 42 | .type('testtest') 43 | .should('have.value', 'testtest'); 44 | 45 | cy.get('input[name=firstname]') 46 | .type('Jack') 47 | .should('have.value', 'Jack'); 48 | 49 | cy.get('input[name=lastname]') 50 | .type('Lang') 51 | .should('have.value', 'Lang'); 52 | 53 | cy.contains('Commencez').click(); 54 | 55 | cy.url().should('include', '/app/onboarding'); 56 | 57 | cy.contains('Nous avons besoin de quelques'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /cypress/screenshots/client_spec.js/Client -- should allow to create a client (failed).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/cypress/screenshots/client_spec.js/Client -- should allow to create a client (failed).png -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add("login", (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This is will overwrite an existing command -- 25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 26 | 27 | import 'cypress-drag-drop'; 28 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands' 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /cypress/videos/client_spec.js-compressed.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/cypress/videos/client_spec.js-compressed.mp4 -------------------------------------------------------------------------------- /cypress/videos/client_spec.js.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/cypress/videos/client_spec.js.mp4 -------------------------------------------------------------------------------- /fetchFragment.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | const fs = require('fs'); 3 | 4 | fetch(`https://prisma-dev.inyo.me/`, { 5 | method: 'POST', 6 | headers: { 'Content-Type': 'application/json' }, 7 | body: JSON.stringify({ 8 | variables: {}, 9 | query: ` 10 | { 11 | __schema { 12 | types { 13 | kind 14 | name 15 | possibleTypes { 16 | name 17 | } 18 | } 19 | } 20 | } 21 | `, 22 | }), 23 | }) 24 | .then(result => result.json()) 25 | .then(result => { 26 | // here we're filtering out any type information unrelated to unions or interfaces 27 | const filteredData = result.data.__schema.types.filter( 28 | type => type.possibleTypes !== null, 29 | ); 30 | result.data.__schema.types = filteredData; 31 | fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => { 32 | if (err) { 33 | console.error('Error writing fragmentTypes file', err); 34 | } else { 35 | console.log('Fragment types successfully extracted!'); 36 | } 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /public/community-inyo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/community-inyo.png -------------------------------------------------------------------------------- /public/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /public/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /public/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /public/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/apple-icon.png -------------------------------------------------------------------------------- /public/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/favicon.ico -------------------------------------------------------------------------------- /public/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/fonts/Ligne-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/fonts/Ligne-Bold.otf -------------------------------------------------------------------------------- /public/fonts/Ligne-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/fonts/Ligne-Medium.otf -------------------------------------------------------------------------------- /public/fonts/Ligne-SemiBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/public/fonts/Ligne-SemiBold.otf -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /sentry-release-script: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | VERSION=`sentry-cli releases propose-version` 3 | sentry-cli releases new "$VERSION" 4 | sentry-cli releases set-commits "$VERSION" --auto 5 | sentry-cli releases finalize "$VERSION" 6 | sentry-cli releases deploys "$VERSION" new -e production 7 | -------------------------------------------------------------------------------- /src/HOC/withTracker.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import ReactGA from 'react-ga'; 3 | 4 | const TrackComponent = ({options, location, children}) => { 5 | const page = location.pathname + location.search; 6 | 7 | useEffect(() => { 8 | ReactGA.set({ 9 | page, 10 | ...options 11 | }); 12 | ReactGA.pageview(page); 13 | }, [page]); 14 | 15 | return children; 16 | }; 17 | 18 | export default (WrappedComponent, options = {}) => { 19 | return props => ( 20 | 21 | 22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/Storyshots.test.js: -------------------------------------------------------------------------------- 1 | import initStoryshots from '@storybook/addon-storyshots'; 2 | 3 | initStoryshots(); 4 | -------------------------------------------------------------------------------- /src/__snapshots__/Storyshots.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Storyshots TaskInput default 1`] = ` 4 |
7 |
11 |
14 |
19 | ▾ 20 |
21 | 32 | 33 |
34 |
35 | `; 36 | -------------------------------------------------------------------------------- /src/components/Apostrophe/index.js: -------------------------------------------------------------------------------- 1 | export default function ({withVowel, withConsonant, value = ''}) { 2 | const startWithVowel = value 3 | .toLowerCase() 4 | .normalize('NFD') 5 | .match(/^[aiueo]/); 6 | 7 | return startWithVowel ? withVowel : withConsonant; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/AssignedToOtherCard/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React, {forwardRef} from 'react'; 3 | import {Link, withRouter} from 'react-router-dom'; 4 | 5 | import {primaryBlack, primaryGrey} from '../../utils/new/design-system'; 6 | import Icon from '../MaterialIcon'; 7 | 8 | const Name = styled('div')` 9 | ${props => props.done && 'text-decoration: line-through;'} 10 | `; 11 | 12 | const CardElem = styled(Link)` 13 | color: ${primaryGrey}; 14 | padding: 5px; 15 | font-size: 0.75rem; 16 | line-height: 1; 17 | cursor: pointer; 18 | display: flex; 19 | align-items: flex-start; 20 | text-decoration: none; 21 | 22 | transition: all 300ms ease; 23 | 24 | &:hover { 25 | color: ${primaryBlack}; 26 | ${props => !props.done && 'text-decoration: underline;'} 27 | } 28 | `; 29 | 30 | const AssignedToOtherCard = withRouter(({ 31 | task, location, cardRef, ...rest 32 | }) => ( 33 | 41 | 46 | {task.name} 47 | 48 | )); 49 | 50 | export default forwardRef((props, ref) => ( 51 | 52 | )); 53 | -------------------------------------------------------------------------------- /src/components/BistableButton/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import {Button} from '../../utils/new/design-system'; 4 | import Tooltip from '../Tooltip'; 5 | 6 | const BistableButton = ({ 7 | value, 8 | disabled, 9 | trueLabel, 10 | trueTooltip, 11 | falseLabel, 12 | falseTooltip, 13 | commit, 14 | reverse, 15 | white, 16 | primary, 17 | id, 18 | }) => ( 19 | 20 | 37 | 38 | ); 39 | 40 | export default BistableButton; 41 | -------------------------------------------------------------------------------- /src/components/CollabLinkToProjectList/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | 4 | import {FlexRow} from '../../utils/content'; 5 | import {formatName} from '../../utils/functions'; 6 | import CollaboratorAvatar from '../CollaboratorAvatar'; 7 | 8 | const CollaboratorEmail = styled('div')` 9 | align-self: center; 10 | `; 11 | 12 | const CollabLinkToProject = styled(FlexRow)` 13 | margin-top: 0.5rem; 14 | `; 15 | 16 | const CollabLinkToProjectList = ({project}) => ( 17 |
18 | {project.linkedCollaborators.map(collaborator => ( 19 | 20 | 21 | 22 | {formatName(collaborator.firstName, collaborator.lastName)} 23 | 24 | 25 | ))} 26 |
27 | ); 28 | 29 | export default CollabLinkToProjectList; 30 | -------------------------------------------------------------------------------- /src/components/CollaboratorAvatar/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | 4 | import InitialIdentifier from '../InitialIdentifier'; 5 | 6 | const CollaboratorAvatarContainer = styled('div')` 7 | border-radius: 50%; 8 | height: ${props => props.size}px; 9 | width: ${props => props.size}px; 10 | margin-right: 1rem; 11 | overflow: hidden; 12 | `; 13 | 14 | function CollaboratorAvatar({collaborator, size = 50}) { 15 | return ( 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default CollaboratorAvatar; 23 | -------------------------------------------------------------------------------- /src/components/CollaboratorList/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | 4 | import {useMutation} from '../../utils/apollo-hooks'; 5 | import {BREAKPOINTS} from '../../utils/constants'; 6 | import {formatName} from '../../utils/functions'; 7 | import {LINK_TO_PROJECT, REMOVE_LINK_TO_PROJECT} from '../../utils/mutations'; 8 | import {Button, CollaboratorLineRow} from '../../utils/new/design-system'; 9 | import CollaboratorAvatar from '../CollaboratorAvatar'; 10 | import MaterialIcon from '../MaterialIcon'; 11 | 12 | const Actions = styled('div')` 13 | display: flex; 14 | flex-direction: row; 15 | align-items: center; 16 | justify-content: flex-end; 17 | flex: 1; 18 | 19 | * ~ * { 20 | margin-left: 2rem; 21 | } 22 | 23 | @media (max-width: ${BREAKPOINTS.mobile}px) { 24 | flex-direction: column-reverse; 25 | justify-content: flex-start; 26 | 27 | * ~ * { 28 | margin-left: 0; 29 | margin-bottom: 0.5rem; 30 | } 31 | } 32 | `; 33 | 34 | function CollaboratorLine({collaborator: {collaborator, isLinked}, projectId}) { 35 | const [linkToProject] = useMutation(LINK_TO_PROJECT); 36 | const [removeLinkToProject] = useMutation(REMOVE_LINK_TO_PROJECT); 37 | 38 | return ( 39 | { 41 | if (isLinked) { 42 | removeLinkToProject({ 43 | variables: { 44 | collaboratorId: collaborator.id, 45 | projectId, 46 | }, 47 | }); 48 | } 49 | else { 50 | linkToProject({ 51 | variables: { 52 | collaboratorId: collaborator.id, 53 | projectId, 54 | }, 55 | }); 56 | } 57 | }} 58 | > 59 | 60 |
61 | {formatName(collaborator.firstName, collaborator.lastName)} 62 |
63 | 64 | {isLinked && } 65 | 66 |
67 | ); 68 | } 69 | 70 | function CollaboratorList({collaborators, projectId}) { 71 | return ( 72 |
73 | {collaborators.map(c => ( 74 | 75 | ))} 76 |
77 | ); 78 | } 79 | 80 | export default CollaboratorList; 81 | -------------------------------------------------------------------------------- /src/components/Comment/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | import {Remarkable} from 'remarkable'; 4 | import {linkify} from 'remarkable/linkify'; 5 | 6 | import { 7 | FlexColumn, FlexRow, gray70, gray80, 8 | } from '../../utils/content'; 9 | import InitialIdentifier from '../InitialIdentifier'; 10 | 11 | const CommentMain = styled('div')` 12 | margin: 20px 0; 13 | `; 14 | const CommentInfo = styled('time')` 15 | font-size: 13px; 16 | margin-bottom: 5px; 17 | color: ${gray70}; 18 | `; 19 | const CommentText = styled('div')` 20 | padding-top: 2px; 21 | font-size: 15px; 22 | line-height: 1.5; 23 | color: ${gray80}; 24 | p { 25 | margin-top: 0; 26 | } 27 | `; 28 | const CommentContent = styled(FlexColumn)` 29 | margin-left: 20px; 30 | `; 31 | 32 | const md = new Remarkable({breaks: true}).use(linkify); 33 | 34 | function Comment({comment: {text, author, createdAt}}) { 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | {new Date(createdAt).toLocaleString()} 42 | 43 | 49 | 50 | 51 | 52 | ); 53 | } 54 | 55 | export default Comment; 56 | -------------------------------------------------------------------------------- /src/components/ConfirmFinishCustomerTaskModal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import fbt from '../../fbt/fbt.macro'; 4 | import {P} from '../../utils/new/design-system'; 5 | import ConfirmModal from '../ConfirmModal'; 6 | 7 | const ConfirmFinishCustomerTaskModal = props => ( 8 | 9 |

10 | 11 | Vous êtes sur le point de valider une tâche que votre client 12 | aurait du effectué par le biais de rappels automatiques. 13 | 14 |

15 |

16 | 20 | Souhaitez-vous continuer ? 21 | 22 |

23 |
24 | ); 25 | 26 | export default ConfirmFinishCustomerTaskModal; 27 | -------------------------------------------------------------------------------- /src/components/ConfirmModal/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React, {useState} from 'react'; 3 | 4 | import fbt from '../../fbt/fbt.macro'; 5 | import {ModalContainer, ModalElem} from '../../utils/content'; 6 | import {Button} from '../../utils/new/design-system'; 7 | 8 | const ModalRow = styled('div')` 9 | padding: 1rem 2rem; 10 | display: flex; 11 | flex-direction: column; 12 | `; 13 | 14 | const ModalRowHoriz = styled(ModalRow)` 15 | display: flex; 16 | flex-direction: row; 17 | justify-content: flex-end; 18 | `; 19 | 20 | export const useConfirmation = () => { 21 | const [promise, setPromise] = useState(); 22 | 23 | return [ 24 | promise ? promise.resolve : false, 25 | async () => { 26 | const newPromise = new Promise((resolve) => { 27 | setPromise({resolve}); 28 | }); 29 | 30 | const confirmed = await newPromise; 31 | 32 | setPromise(); 33 | 34 | return confirmed; 35 | }, 36 | ]; 37 | }; 38 | 39 | export default function ConfirmModal({ 40 | children, 41 | onConfirm, 42 | closeModal, 43 | onDismiss, 44 | }) { 45 | return ( 46 | 47 | 48 | {children} 49 | 50 | 55 | 60 | 61 | 62 | 63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /src/components/CreateProjectLinkButton/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React, {useState} from 'react'; 3 | 4 | import fbt from '../../fbt/fbt.macro'; 5 | import { 6 | FlexColumn, 7 | ModalActions, 8 | ModalContainer, 9 | ModalElem, 10 | } from '../../utils/content'; 11 | import { 12 | Button, Input, P, SubHeading, 13 | } from '../../utils/new/design-system'; 14 | import MaterialIcon from '../MaterialIcon'; 15 | 16 | const ModalContent = styled(FlexColumn)` 17 | margin-top: 2rem; 18 | `; 19 | 20 | function CreateProjectLinkButton({project}) { 21 | const [openLinkModal, setOpenLinkModal] = useState(false); 22 | const projectId = project.id; 23 | const customerToken = project.customer 24 | ? project.customer.token 25 | : project.token; 26 | 27 | const {protocol, host} = window.location; 28 | 29 | return ( 30 | <> 31 | 40 | {openLinkModal && ( 41 | setOpenLinkModal(false)} 44 | > 45 | 46 | 47 | 51 | Lien pour partager votre projet avec votre 52 | client 53 | 54 | 55 | 56 | {!project.customer && ( 57 |

58 | 59 | Attention : Ce lien permet seulement de 60 | consulter le projet. Ajoutez un client 61 | pour générer un nouveau lien qui 62 | autorise les modifications. 63 | 64 |

65 | )} 66 | 70 |
71 | 72 | 80 | 81 |
82 |
83 | )} 84 | 85 | ); 86 | } 87 | 88 | export default CreateProjectLinkButton; 89 | -------------------------------------------------------------------------------- /src/components/CustomEmailSidebar/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | 4 | import CustomEmailSidebarCategory from '../CustomEmailSidebarCategory'; 5 | 6 | const CustomEmailSidebarContainer = styled('div')` 7 | flex: 0 0 250px; 8 | margin-right: 2rem; 9 | margin-top: 2rem; 10 | `; 11 | 12 | const CustomEmailSidebar = ({categories}) => ( 13 | 14 | {categories.map((category, index) => ( 15 | 16 | ))} 17 | 18 | ); 19 | 20 | export default CustomEmailSidebar; 21 | -------------------------------------------------------------------------------- /src/components/CustomEmailTimingInput/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled/macro'; 2 | import React from 'react'; 3 | 4 | import fbt from '../../fbt/fbt.macro'; 5 | import {BREAKPOINTS} from '../../utils/constants'; 6 | import TimingInput from '../TimingInput'; 7 | 8 | const Container = styled('span')` 9 | display: flex; 10 | align-items: center; 11 | 12 | @media (max-width: ${BREAKPOINTS.mobile}px) { 13 | display: grid; 14 | } 15 | `; 16 | 17 | const CustomEmailTimingInput = ({ 18 | unit, 19 | value, 20 | isRelative, 21 | setValue, 22 | setUnit, 23 | setIsRelative, 24 | relativeDisabled, 25 | }) => ( 26 | 27 | Cet email est envoyé{' '} 28 | 37 | 38 | ); 39 | 40 | export default CustomEmailTimingInput; 41 | -------------------------------------------------------------------------------- /src/components/CustomerModalAndMail/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | 3 | import CustomerIntroMail from '../CustomerIntroMail'; 4 | import CustomerModal from '../CustomerModal'; 5 | 6 | function CustomerModalAndMail({ 7 | onDismiss, 8 | onValidate, 9 | customer, 10 | close, 11 | ...rest 12 | }) { 13 | const [createdCustomer, setCreatedCustomer] = useState(false); 14 | 15 | return createdCustomer ? ( 16 | { 19 | onValidate(createdCustomer); 20 | close(); 21 | }} 22 | /> 23 | ) : ( 24 | { 30 | onValidate(customer); 31 | setCreatedCustomer(customer); 32 | }} 33 | /> 34 | ); 35 | } 36 | 37 | export default CustomerModalAndMail; 38 | -------------------------------------------------------------------------------- /src/components/CustomerNameAndAddress/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled/macro'; 2 | import React from 'react'; 3 | 4 | import {H4, H5} from '../../utils/content'; 5 | import {formatFullName} from '../../utils/functions'; 6 | import {gray70, P, primaryBlack} from '../../utils/new/design-system'; 7 | 8 | const ClientAddress = styled('div')` 9 | margin: 20px 0; 10 | `; 11 | 12 | const ContactName = styled(H5)` 13 | font-size: 14px; 14 | font-weight: 400; 15 | color: ${gray70}; 16 | `; 17 | 18 | const ContactInfo = styled(P)` 19 | font-size: 14px; 20 | line-height: 1.4; 21 | color: ${gray70}; 22 | margin-top: 0; 23 | `; 24 | 25 | const CompanyName = styled(H4)` 26 | font-size: 16px; 27 | font-weight: 500; 28 | color: ${primaryBlack}; 29 | 30 | & + ${ContactName} { 31 | margin-bottom: 0; 32 | } 33 | `; 34 | 35 | const CustomerNameAndAddress = ({ 36 | customer: { 37 | name, firstName, lastName, email, title, phone, address, 38 | }, 39 | }) => ( 40 | 41 | {name} 42 | {formatFullName(title, firstName, lastName)} 43 | {address && ( 44 | <> 45 | 46 | {address.street} 47 | 48 | 49 | {address.postalCode} {address.city}, {address.country} 50 | 51 | 52 | )} 53 | {email} 54 | {phone} 55 | 56 | ); 57 | 58 | export default CustomerNameAndAddress; 59 | -------------------------------------------------------------------------------- /src/components/CustomersDropdown/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import fbt from '../../fbt/fbt.macro'; 4 | import {useQuery} from '../../utils/apollo-hooks'; 5 | import {formatName} from '../../utils/functions'; 6 | import {GET_ALL_CUSTOMERS} from '../../utils/queries'; 7 | import {ArianneElem} from '../ArianneThread'; 8 | 9 | const CustomersDropdown = ({creatable, ...props}) => { 10 | const {data, errors} = useQuery(GET_ALL_CUSTOMERS, {suspend: true}); 11 | 12 | if (errors) throw errors; 13 | const customers = data.me.customers.map(customer => ({ 14 | ...customer, 15 | name: `${customer.name} (${formatName( 16 | customer.firstName, 17 | customer.lastName, 18 | )})`, 19 | })); 20 | 21 | if (creatable) { 22 | customers.unshift({ 23 | id: 'CREATE', 24 | name: ( 25 | 29 | Créer un nouveau client 30 | 31 | ), 32 | }); 33 | } 34 | 35 | return ; 36 | }; 37 | 38 | export default CustomersDropdown; 39 | -------------------------------------------------------------------------------- /src/components/DeadlineCard/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React, {forwardRef} from 'react'; 3 | import {Link, withRouter} from 'react-router-dom'; 4 | 5 | import {primaryBlack, primaryGrey} from '../../utils/new/design-system'; 6 | import Icon from '../MaterialIcon'; 7 | 8 | const CardElem = styled(Link)` 9 | color: ${primaryGrey}; 10 | padding: 5px; 11 | font-size: 0.75rem; 12 | line-height: 1; 13 | cursor: pointer; 14 | display: flex; 15 | align-items: flex-start; 16 | text-decoration: none; 17 | 18 | transition: all 300ms ease; 19 | 20 | &:hover { 21 | color: ${primaryBlack}; 22 | ${props => !props.done && 'text-decoration: underline;'} 23 | } 24 | `; 25 | 26 | const DeadlineCard = withRouter( 27 | ({ 28 | date, task, project, location, cardRef, ...rest 29 | }) => ( 30 | 46 | 51 | {project ? project.name : task.name} 52 | 53 | ), 54 | ); 55 | 56 | export default forwardRef((props, ref) => ( 57 | 58 | )); 59 | -------------------------------------------------------------------------------- /src/components/DefaultDroppableDay/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | import {useDrop} from 'react-dnd'; 4 | 5 | import {DRAG_TYPES} from '../../utils/constants'; 6 | import {DragSeparator} from '../../utils/new/design-system'; 7 | 8 | const Content = styled('div')` 9 | position: relative; 10 | flex: 1 0 70px; 11 | display: flex; 12 | justify-content: center; 13 | align-items: baseline; 14 | 15 | ${props => props.isOver 16 | && ` 17 | background: rgba(242,242,242,.4); 18 | transitions: all 400ms ease; 19 | border-radius: 8px; 20 | `} 21 | `; 22 | 23 | function DefaultDroppableDay({ 24 | index, 25 | scheduledFor, 26 | onMove, 27 | separator = true, 28 | children, 29 | ...props 30 | }) { 31 | const [{isOver}, drop] = useDrop({ 32 | accept: DRAG_TYPES.TASK, 33 | collect(monitor) { 34 | return { 35 | isOver: monitor.isOver(), 36 | }; 37 | }, 38 | drop(item) { 39 | if (typeof item.index !== 'number') { 40 | return onMove({ 41 | id: item.id, 42 | type: item.type, 43 | linkedCustomer: item.linkedCustomer, // we need this 44 | attachments: item.attachments, // and this to check for activation criteria fulfillment 45 | scheduledFor, 46 | }); 47 | } 48 | return {index, scheduledFor}; 49 | }, 50 | }); 51 | 52 | return ( 53 | 54 | {isOver && separator && } 55 | {React.Children.map(children, (child) => { 56 | if (!child) return null; 57 | return React.cloneElement(child, {isOver, ...props}); 58 | })} 59 | 60 | ); 61 | } 62 | 63 | export default DefaultDroppableDay; 64 | -------------------------------------------------------------------------------- /src/components/DuplicateProjectButton/index.js: -------------------------------------------------------------------------------- 1 | import React, {forwardRef, useState} from 'react'; 2 | 3 | import {useMutation, useQuery} from '../../utils/apollo-hooks'; 4 | import {CREATE_PROJECT} from '../../utils/mutations'; 5 | import {Button, Input, P} from '../../utils/new/design-system'; 6 | import {GET_PROJECT_DATA} from '../../utils/queries'; 7 | import ConfirmModal from '../ConfirmModal'; 8 | 9 | const DuplicateProjectModal = ({ 10 | projectId, onCreate, onConfirm, ...rest 11 | }) => { 12 | const {data, error} = useQuery(GET_PROJECT_DATA, { 13 | variables: {projectId}, 14 | suspend: true, 15 | }); 16 | const [duplicateProject] = useMutation(CREATE_PROJECT); 17 | const [name, setName] = useState(false); 18 | 19 | if (error) throw error; 20 | 21 | const {project} = data; 22 | 23 | return ( 24 | { 26 | if (confirmed) { 27 | const sections = project.sections.map(section => ({ 28 | name: section.name, 29 | items: section.items.map(item => ({ 30 | name: item.name, 31 | description: item.description, 32 | type: item.type, 33 | unit: item.timeItTook || item.unit || 0, 34 | })), 35 | })); 36 | 37 | const { 38 | data: {createProject}, 39 | } = await duplicateProject({ 40 | variables: { 41 | template: project.template, 42 | name, 43 | sections, 44 | customerId: project.customer && project.customer.id, 45 | deadline: project.deadline, 46 | }, 47 | }); 48 | 49 | onCreate(createProject); 50 | } 51 | }} 52 | {...rest} 53 | > 54 |

Titre du nouveau projet

55 | setName(e.target.value)} /> 56 |
57 | ); 58 | }; 59 | 60 | const DuplicateProjectButton = forwardRef( 61 | ({ 62 | children, projectId, onCreate, ...rest 63 | }, ref) => { 64 | const [isOpen, toggleModal] = useState(false); 65 | 66 | return ( 67 | <> 68 | 71 | 72 | {isOpen && ( 73 | toggleModal(confirmed)} 77 | closeModal={() => toggleModal(false)} 78 | /> 79 | )} 80 | 81 | ); 82 | }, 83 | ); 84 | 85 | export default DuplicateProjectButton; 86 | -------------------------------------------------------------------------------- /src/components/EmailExample/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled/macro'; 2 | import React from 'react'; 3 | 4 | import fbt from '../../fbt/fbt.macro'; 5 | import {accentGrey} from '../../utils/new/design-system'; 6 | 7 | const SubjectAndToElem = styled('div')` 8 | display: flex; 9 | ${props => (props.noBorder ? '' : `border-bottom: solid 1px ${accentGrey}`)}; 10 | ${props => (props.noBorder ? '' : 'padding-bottom: 1rem;')}; 11 | margin-bottom: 1rem; 12 | `; 13 | 14 | const SubjectAndToLabel = styled('div')` 15 | color: ${accentGrey}; 16 | margin-right: 1rem; 17 | `; 18 | 19 | const SubjectAndToInfo = styled('div')``; 20 | 21 | function EmailExample({ 22 | subject, email, children, userEmail, 23 | }) { 24 | return ( 25 |
26 | 27 | 28 | 29 | De 30 | 31 | 32 | {userEmail} 33 | 34 | 35 | 36 | 37 | À 38 | 39 | 40 | {email} 41 | 42 | 43 | 44 | 45 | Sujet 46 | 47 | 48 | {subject} 49 | 50 | {children} 51 |
52 | ); 53 | } 54 | 55 | export default EmailExample; 56 | -------------------------------------------------------------------------------- /src/components/EmailParam/index.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/byte-foundry/inyo/967523566a0986fcd45e078f54f4cc46733e9ea9/src/components/EmailParam/index.js -------------------------------------------------------------------------------- /src/components/EmailParamList/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React from 'react'; 3 | 4 | import {LABEL_EMAIL_PARAM} from '../../utils/constants'; 5 | import {FlexRow} from '../../utils/content'; 6 | import { 7 | lightGrey, 8 | mediumGrey, 9 | primaryBlack, 10 | } from '../../utils/new/design-system'; 11 | 12 | const Param = styled('div')` 13 | margin: 0 0 0.5rem 0.5rem; 14 | background: ${props => (props.used ? 'rgba(34, 201, 121, 0.3)' : lightGrey)}; 15 | color: ${props => (props.used ? '#22C979' : '#505050')}; 16 | border-radius: 20px; 17 | padding: 8px 18px; 18 | font-size: 12px; 19 | 20 | &:hover { 21 | background: ${props => (props.used ? 'rgba(34, 201, 121, 0.5);' : mediumGrey)}; 22 | color: ${props => (props.used ? '#059062' : primaryBlack)}; 23 | cursor: pointer; 24 | } 25 | `; 26 | 27 | const EmailParamListContainer = styled(FlexRow)` 28 | flex-wrap: wrap; 29 | margin-top: 2rem; 30 | `; 31 | 32 | const EmailParamList = ({params, editor, paramsUsed}) => ( 33 | 34 | {params.map(param => ( 35 | { 38 | e.preventDefault(); 39 | editor.insertInlineAtRange(editor.value.selection, { 40 | data: {param: param.param}, 41 | nodes: [ 42 | { 43 | object: 'text', 44 | leaves: [ 45 | { 46 | text: param.param.name, 47 | }, 48 | ], 49 | }, 50 | ], 51 | type: 'param', 52 | }); 53 | 54 | // can't seem to get focus any other way 55 | setTimeout(() => editor.focus(), 0); 56 | }} 57 | > 58 | {LABEL_EMAIL_PARAM[param.param.name].text()} 59 | 60 | ))} 61 | 62 | ); 63 | 64 | export default EmailParamList; 65 | -------------------------------------------------------------------------------- /src/components/FormCheckbox/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import {Field} from 'formik'; 3 | import React, {Component} from 'react'; 4 | 5 | import {ErrorInput, FlexRow, Label} from '../../utils/content'; 6 | 7 | const FormCheckboxMain = styled(FlexRow)``; 8 | const CheckboxLabel = styled('span')` 9 | margin-left: 10px; 10 | `; 11 | 12 | class FormCheckbox extends Component { 13 | render() { 14 | const { 15 | name, 16 | label, 17 | values, 18 | handleBlur, 19 | errors, 20 | touched, 21 | required, 22 | onChange, 23 | manual, 24 | } = this.props; 25 | 26 | return ( 27 | 28 | {this.props.label && ( 29 | 30 | {({form}) => ( 31 | 48 | )} 49 | 50 | )} 51 | {errors[name] && touched[name] && ( 52 | 53 | {errors[name]} 54 | 55 | )} 56 | 57 | ); 58 | } 59 | } 60 | 61 | FormCheckbox.defaultProps = { 62 | onChange: () => {}, 63 | }; 64 | 65 | export default FormCheckbox; 66 | -------------------------------------------------------------------------------- /src/components/FormElem/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import React, {Component} from 'react'; 3 | 4 | import {ErrorInput} from '../../utils/content'; 5 | import {getDeep} from '../../utils/functions'; 6 | import {InputLabel, Label} from '../../utils/new/design-system'; 7 | import FormInput from '../FormInput'; 8 | 9 | const FormElemMain = styled(InputLabel)` 10 | width: 100%; 11 | margin-bottom: ${props => (props.noMarginBottom ? '0' : '20px')}; 12 | `; 13 | 14 | class FormElem extends Component { 15 | render() { 16 | const { 17 | name, 18 | label, 19 | placeholder, 20 | type, 21 | values, 22 | handleChange, 23 | handleBlur, 24 | errors, 25 | touched, 26 | required, 27 | padded, 28 | inline, 29 | onboarding, 30 | big, 31 | noMarginBottom, 32 | ...rest 33 | } = this.props; 34 | 35 | const hasErrors = getDeep(name, errors) && getDeep(name, touched); 36 | 37 | return ( 38 | 46 | {this.props.label && ( 47 | 50 | )} 51 | 63 | {hasErrors && ( 64 | 65 | {getDeep(name, errors)} 66 | 67 | )} 68 | 69 | ); 70 | } 71 | } 72 | 73 | export default FormElem; 74 | -------------------------------------------------------------------------------- /src/components/FormInput/index.js: -------------------------------------------------------------------------------- 1 | import {css} from '@emotion/core'; 2 | import styled from '@emotion/styled'; 3 | import {Field} from 'formik'; 4 | import React from 'react'; 5 | 6 | import {getDeep} from '../../utils/functions'; 7 | import {Input, primaryWhite} from '../../utils/new/design-system'; 8 | 9 | const FormInputMain = styled(Input)` 10 | display: block; 11 | width: 100%; 12 | box-sizing: border-box; 13 | ${props => props.big && 'height: 40px;'} 14 | 15 | ${props => 16 | props.inline && 17 | css` 18 | background: ${primaryWhite}; 19 | padding: 0; 20 | width: auto; 21 | text-align: center; 22 | `}; 23 | `; 24 | 25 | const FormInput = ({ 26 | name, 27 | handleChange, 28 | handleBlur, 29 | errors, 30 | touched, 31 | ...rest 32 | }) => ( 33 | 41 | {({field, form: {isSubmitting}}) => ( 42 | 49 | )} 50 | 51 | ); 52 | 53 | export default FormInput; 54 | -------------------------------------------------------------------------------- /src/components/FormSelect/index.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | import {Field} from 'formik'; 3 | import React from 'react'; 4 | 5 | import {ErrorInput} from '../../utils/content'; 6 | import {getDeep} from '../../utils/functions'; 7 | import {InputLabel, Label, Select} from '../../utils/new/design-system'; 8 | 9 | const FormSelectMain = styled(InputLabel)``; 10 | 11 | function FormSelect({ 12 | name, 13 | label, 14 | handleBlur, 15 | values, 16 | errors, 17 | touched, 18 | required, 19 | inline, 20 | onboarding, 21 | options, 22 | css, 23 | style, 24 | onChange, 25 | ...rest 26 | }) { 27 | let value; 28 | 29 | options.forEach((option) => { 30 | if (option.options) { 31 | const matchingOption = option.options.find( 32 | suboption => suboption.value === values[name], 33 | ); 34 | 35 | if (matchingOption) { 36 | value = matchingOption; 37 | } 38 | } 39 | else if (option.value === values[name]) { 40 | value = option; 41 | } 42 | }); 43 | 44 | return ( 45 | 53 | {label && ( 54 | 57 | )} 58 | 59 | {({form}) => ( 60 |