├── src ├── assets │ ├── .gitkeep │ └── img │ │ ├── app │ │ └── no-image.png │ │ ├── vector │ │ └── office-env2.jpg │ │ ├── api │ │ └── gmail │ │ │ └── Gmail-Logo.png │ │ └── logo │ │ └── careydevelopment-logo-sm.png ├── app │ ├── app.component.css │ ├── home │ │ ├── home.component.css │ │ ├── home.component.html │ │ ├── home.component.ts │ │ ├── home.module.ts │ │ └── home.component.spec.ts │ ├── login │ │ ├── login.component.css │ │ ├── login.component.ts │ │ ├── login.module.ts │ │ ├── login.component.spec.ts │ │ └── login.component.html │ ├── ui │ │ ├── breadcrumb │ │ │ ├── breadcrumb.component.css │ │ │ ├── breadcrumb.ts │ │ │ ├── breadcrumb.service.ts │ │ │ ├── breadcrumb.module.ts │ │ │ ├── breadcrumb.component.html │ │ │ └── breadcrumb.component.spec.ts │ │ └── confirmation-dialog │ │ │ ├── confirmation-dialog.component.css │ │ │ ├── confirmation-dialog.ts │ │ │ ├── confirmation-dialog.component.html │ │ │ ├── confirmation-dialog.component.ts │ │ │ └── confirmation-dialog.component.spec.ts │ ├── features │ │ ├── dashboard │ │ │ ├── dashboard.component.css │ │ │ ├── dashboard.component.ts │ │ │ ├── dashboard.component.html │ │ │ ├── dashboard.component.spec.ts │ │ │ └── dashboard.module.ts │ │ ├── deals │ │ │ ├── add-deal │ │ │ │ ├── add-deal.component.css │ │ │ │ ├── add-deal.component.html │ │ │ │ ├── add-deal.component.spec.ts │ │ │ │ └── add-deal.component.ts │ │ │ ├── edit-deal │ │ │ │ ├── edit-deal.component.css │ │ │ │ ├── edit-deal.component.html │ │ │ │ ├── edit-deal.component.spec.ts │ │ │ │ └── edit-deal.component.ts │ │ │ ├── charts │ │ │ │ ├── accounts-ranked │ │ │ │ │ ├── accounts-ranked.component.css │ │ │ │ │ ├── accounts-ranked.component.html │ │ │ │ │ └── accounts-ranked.component.spec.ts │ │ │ │ ├── future-pipeline │ │ │ │ │ ├── future-pipeline.component.css │ │ │ │ │ ├── future-pipeline.component.html │ │ │ │ │ └── future-pipeline.component.spec.ts │ │ │ │ ├── deal-share-by-contact │ │ │ │ │ ├── deal-share-by-contact.component.css │ │ │ │ │ └── deal-share-by-contact.component.html │ │ │ │ └── revenue-contribution │ │ │ │ │ ├── revenue-contribution.component.css │ │ │ │ │ ├── revenue-contribution.component.html │ │ │ │ │ └── revenue-contribution.component.spec.ts │ │ │ ├── stage-progress-bar │ │ │ │ ├── stage-progress-bar.component.css │ │ │ │ ├── stage-progress-bar.component.html │ │ │ │ ├── stage-progress-bar.component.spec.ts │ │ │ │ └── stage-progress-bar.component.ts │ │ │ ├── deal-form │ │ │ │ ├── deal-form.component.css │ │ │ │ └── deal-form.component.spec.ts │ │ │ ├── models │ │ │ │ ├── sales-type.ts │ │ │ │ ├── account-lightweight.ts │ │ │ │ ├── deal-lightweight.ts │ │ │ │ ├── deal-stage.ts │ │ │ │ ├── price.ts │ │ │ │ ├── sales-owner-lightweight.ts │ │ │ │ ├── product.ts │ │ │ │ ├── deal-criteria.ts │ │ │ │ ├── contact-lightweight.ts │ │ │ │ └── deal.ts │ │ │ ├── constants │ │ │ │ ├── unit-type.ts │ │ │ │ └── price-type.ts │ │ │ ├── deals-by-contact │ │ │ │ ├── deals-by-contact.component.css │ │ │ │ ├── deals-by-contact.component.spec.ts │ │ │ │ └── deals-by-contact.component.ts │ │ │ ├── view-deals │ │ │ │ ├── view-deals.component.css │ │ │ │ └── view-deals.component.spec.ts │ │ │ ├── view-deal │ │ │ │ ├── view-deal.component.css │ │ │ │ └── view-deal.component.spec.ts │ │ │ ├── service │ │ │ │ └── product.service.ts │ │ │ └── directives │ │ │ │ └── display-stage.directive.ts │ │ ├── user │ │ │ ├── email │ │ │ │ ├── message │ │ │ │ │ ├── message.component.css │ │ │ │ │ ├── message.component.spec.ts │ │ │ │ │ ├── message.component.html │ │ │ │ │ └── message.component.ts │ │ │ │ ├── email-choice │ │ │ │ │ ├── email-choice.component.css │ │ │ │ │ ├── email-choice.component.html │ │ │ │ │ ├── email-choice.component.spec.ts │ │ │ │ │ └── email-choice.component.ts │ │ │ │ ├── compose-email │ │ │ │ │ ├── compose-email.component.css │ │ │ │ │ ├── compose-email.component.spec.ts │ │ │ │ │ └── compose-email.component.html │ │ │ │ ├── email-redirect │ │ │ │ │ ├── email-redirect.component.css │ │ │ │ │ ├── email-redirect.component.html │ │ │ │ │ ├── email-redirect.component.spec.ts │ │ │ │ │ └── email-redirect.component.ts │ │ │ │ ├── models │ │ │ │ │ ├── email-integration.ts │ │ │ │ │ ├── token-request.ts │ │ │ │ │ ├── google-api-error.ts │ │ │ │ │ └── email.ts │ │ │ │ └── inbox │ │ │ │ │ ├── inbox.component.spec.ts │ │ │ │ │ └── inbox.component.css │ │ │ ├── account-info │ │ │ │ ├── account-info.component.css │ │ │ │ └── account-info.component.spec.ts │ │ │ └── profile-image │ │ │ │ ├── profile-image.component.css │ │ │ │ ├── profile-image.component.html │ │ │ │ └── profile-image.component.spec.ts │ │ ├── accounts │ │ │ ├── add-account │ │ │ │ ├── add-account.component.css │ │ │ │ ├── add-account.component.html │ │ │ │ ├── add-account.component.ts │ │ │ │ └── add-account.component.spec.ts │ │ │ ├── account-form │ │ │ │ ├── account-form.component.css │ │ │ │ └── account-form.component.spec.ts │ │ │ ├── edit-account │ │ │ │ ├── edit-account.component.css │ │ │ │ ├── edit-account.component.html │ │ │ │ └── edit-account.component.spec.ts │ │ │ ├── constants │ │ │ │ ├── account-status.ts │ │ │ │ └── industry.ts │ │ │ ├── view-accounts │ │ │ │ ├── view-accounts.component.css │ │ │ │ └── view-accounts.component.spec.ts │ │ │ ├── models │ │ │ │ └── account.ts │ │ │ ├── view-account │ │ │ │ ├── view-account.component.spec.ts │ │ │ │ └── view-account.component.css │ │ │ └── services │ │ │ │ └── account.service.ts │ │ ├── contacts │ │ │ ├── add-contact │ │ │ │ ├── add-contact.component.css │ │ │ │ ├── add-contact.component.html │ │ │ │ ├── add-contact.component.ts │ │ │ │ └── add-contact.component.spec.ts │ │ │ ├── contact-form │ │ │ │ ├── contact-form.component.css │ │ │ │ ├── phones-form │ │ │ │ │ ├── phones-form.component.css │ │ │ │ │ ├── phone-type-form │ │ │ │ │ │ ├── phone-type-form.component.css │ │ │ │ │ │ ├── phone-type-form.component.spec.ts │ │ │ │ │ │ ├── phone-type-form.component.html │ │ │ │ │ │ └── phone-type-form.component.ts │ │ │ │ │ ├── phones-form.component.html │ │ │ │ │ ├── phones-form.component.spec.ts │ │ │ │ │ └── phones-form.component.ts │ │ │ │ ├── addresses-form │ │ │ │ │ ├── addresses-form.component.css │ │ │ │ │ ├── address-type-form │ │ │ │ │ │ ├── address-type-form.component.css │ │ │ │ │ │ └── address-type-form.component.spec.ts │ │ │ │ │ ├── addresses-form.component.html │ │ │ │ │ ├── addresses-form.component.spec.ts │ │ │ │ │ └── addresses-form.component.ts │ │ │ │ ├── basic-info-form │ │ │ │ │ ├── basic-info-form.component.css │ │ │ │ │ └── basic-info-form.component.spec.ts │ │ │ │ ├── review-form │ │ │ │ │ ├── review-form.component.spec.ts │ │ │ │ │ ├── review-form.component.ts │ │ │ │ │ └── review-form.component.css │ │ │ │ ├── contact-form.component.spec.ts │ │ │ │ └── contact-form.component.html │ │ │ ├── edit-contact │ │ │ │ ├── edit-contact.component.css │ │ │ │ ├── edit-contact.component.html │ │ │ │ └── edit-contact.component.spec.ts │ │ │ ├── status-progress-bar │ │ │ │ ├── status-progress-bar.component.css │ │ │ │ ├── status-progress-bar.component.html │ │ │ │ ├── status-progress-bar.component.spec.ts │ │ │ │ └── status-progress-bar.component.ts │ │ │ ├── view-contact │ │ │ │ ├── view-contact-menu │ │ │ │ │ ├── view-contact-menu.component.css │ │ │ │ │ ├── view-contact-menu.component.html │ │ │ │ │ ├── view-contact-menu.component.ts │ │ │ │ │ └── view-contact-menu.component.spec.ts │ │ │ │ ├── view-contact.component.css │ │ │ │ └── view-contact.component.spec.ts │ │ │ ├── constants │ │ │ │ ├── line-of-business.ts │ │ │ │ └── contact-status.ts │ │ │ ├── view-contacts │ │ │ │ ├── view-contacts.component.css │ │ │ │ └── view-contacts.component.spec.ts │ │ │ ├── models │ │ │ │ └── contact.ts │ │ │ └── services │ │ │ │ ├── contact-validation.service.ts │ │ │ │ └── contact.service.ts │ │ ├── activities │ │ │ ├── add-activity │ │ │ │ ├── add-activity.component.css │ │ │ │ ├── add-activity.component.html │ │ │ │ ├── add-activity.component.spec.ts │ │ │ │ └── add-activity.component.ts │ │ │ ├── activities-list │ │ │ │ ├── activities-list.component.css │ │ │ │ ├── activities-list.component.spec.ts │ │ │ │ ├── activities-list.component.ts │ │ │ │ └── activities-list.component.html │ │ │ ├── activity-form │ │ │ │ └── activity-form.component.css │ │ │ ├── edit-activity │ │ │ │ ├── edit-activity.component.css │ │ │ │ ├── edit-activity.component.html │ │ │ │ ├── edit-activity.component.spec.ts │ │ │ │ └── edit-activity.component.ts │ │ │ ├── view-activity │ │ │ │ ├── view-activity-menu │ │ │ │ │ ├── view-activity-menu.component.css │ │ │ │ │ ├── view-activity-menu.component.html │ │ │ │ │ └── view-activity-menu.component.ts │ │ │ │ ├── view-activity.component.css │ │ │ │ └── view-activity.component.spec.ts │ │ │ ├── models │ │ │ │ ├── account-lightweight.ts │ │ │ │ ├── activity-outcome.ts │ │ │ │ ├── sales-owner-lightweight.ts │ │ │ │ ├── activity-type-lightweight.ts │ │ │ │ ├── activity-search-criteria.ts │ │ │ │ ├── contact-lightweight.ts │ │ │ │ ├── activity-type.ts │ │ │ │ └── activity.ts │ │ │ ├── recent-activities-by-contact │ │ │ │ ├── recent-activities-by-contact.component.css │ │ │ │ ├── recent-activities-by-contact.component.spec.ts │ │ │ │ ├── recent-activities-by-contact.component.ts │ │ │ │ └── recent-activities-by-contact.component.html │ │ │ ├── constants │ │ │ │ └── activity-status.ts │ │ │ ├── view-activities │ │ │ │ ├── view-activities.component.css │ │ │ │ └── view-activities.component.spec.ts │ │ │ ├── ui │ │ │ │ └── update-notes-dialog │ │ │ │ │ ├── update-notes-dialog.html │ │ │ │ │ └── update-notes-dialog.component.ts │ │ │ ├── directives │ │ │ │ ├── display-activity-date-difference.directive.ts │ │ │ │ └── display-outcome-status.directive.ts │ │ │ └── pipes │ │ │ │ └── activity-date-display.pipe.ts │ │ ├── ui │ │ │ ├── model │ │ │ │ ├── uploaded-image.ts │ │ │ │ ├── nav-item.ts │ │ │ │ └── menu.ts │ │ │ ├── menu-list-item │ │ │ │ ├── menu-list-item.component.css │ │ │ │ ├── menu-list-item.component.html │ │ │ │ └── menu-list-item.component.spec.ts │ │ │ └── service │ │ │ │ ├── display-map.service.ts │ │ │ │ └── nav.service.ts │ │ ├── features.component.css │ │ ├── features.component.html │ │ ├── features.component.spec.ts │ │ ├── features.component.ts │ │ └── service │ │ │ └── file-upload.service.ts │ ├── app.component.html │ ├── models │ │ ├── name-value-map.ts │ │ ├── phone.ts │ │ ├── sales-owner.ts │ │ ├── address.ts │ │ ├── address-type.ts │ │ ├── phone-type.ts │ │ └── source.ts │ ├── util │ │ ├── ui-util.ts │ │ ├── nosanitizepipe.ts │ │ ├── jwt-interceptor.ts │ │ ├── phone-mask.directive.ts │ │ └── http-error-interceptor.ts │ ├── directives │ │ ├── directives.module.ts │ │ ├── single-class-base.directive.ts │ │ └── multiple-classes-base.directive.ts │ ├── services │ │ ├── string.service.ts │ │ ├── currency.service.ts │ │ ├── logout.service.ts │ │ ├── form.service.ts │ │ └── auth-guard.service.ts │ ├── pipes │ │ ├── no-value-display.pipe.ts │ │ └── time-difference.pipe.ts │ ├── app.component.ts │ ├── config │ │ └── validation │ │ │ └── field-summaries.ts │ ├── app.component.spec.ts │ └── app-routing.module.ts ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── testing │ ├── activity-helper.ts │ └── contact-helper.ts ├── tsconfig.app.json ├── tsconfig.spec.json ├── tslint.json ├── main.ts ├── browserslist ├── index.html ├── test.ts └── karma.conf.js ├── .vs ├── slnx.sqlite ├── careydevelopmentcrm │ └── v16 │ │ └── .suo └── VSWorkspaceState.json ├── e2e ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts ├── tsconfig.e2e.json └── protractor.conf.js ├── .editorconfig ├── tsconfig.json ├── .gitignore ├── LICENSE └── package.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/home/home.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/login/login.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/dashboard/dashboard.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/add-deal/add-deal.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/edit-deal/edit-deal.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/user/email/message/message.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |

home works

2 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/accounts/add-account/add-account.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/add-contact/add-contact.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/accounts/account-form/account-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/accounts/edit-account/edit-account.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/activities/add-activity/add-activity.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/contact-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/edit-contact/edit-contact.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/user/account-info/account-info.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-choice/email-choice.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/ui/confirmation-dialog/confirmation-dialog.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/activities/activities-list/activities-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/activities/activity-form/activity-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/activities/edit-activity/edit-activity.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/user/email/compose-email/compose-email.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-redirect/email-redirect.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phones-form.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/accounts-ranked/accounts-ranked.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/future-pipeline/future-pipeline.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/stage-progress-bar/stage-progress-bar.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/status-progress-bar/status-progress-bar.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/addresses-form.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact-menu/view-contact-menu.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/deal-share-by-contact/deal-share-by-contact.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/revenue-contribution/revenue-contribution.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activity/view-activity-menu/view-activity-menu.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vs/slnx.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/.vs/slnx.sqlite -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phone-type-form/phone-type-form.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/features/accounts/add-account/add-account.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/contacts/add-contact/add-contact.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/address-type-form/address-type-form.component.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/features/deals/deal-form/deal-form.component.css: -------------------------------------------------------------------------------- 1 | .amount-display { 2 | color:#3F51B5 3 | } 4 | -------------------------------------------------------------------------------- /src/app/models/name-value-map.ts: -------------------------------------------------------------------------------- 1 | export interface DisplayValueMap { 2 | display: string, 3 | value: string 4 | } 5 | -------------------------------------------------------------------------------- /src/app/features/user/profile-image/profile-image.component.css: -------------------------------------------------------------------------------- 1 | .alert-section { 2 | margin-bottom: 10px; 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/app/features/user/email/models/email-integration.ts: -------------------------------------------------------------------------------- 1 | export interface EmailIntegration { 2 | integrationType: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/app/models/phone.ts: -------------------------------------------------------------------------------- 1 | export interface Phone { 2 | phone: string; 3 | phoneType: string; 4 | countryCode: string; 5 | } 6 | -------------------------------------------------------------------------------- /.vs/careydevelopmentcrm/v16/.suo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/.vs/careydevelopmentcrm/v16/.suo -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/basic-info-form/basic-info-form.component.css: -------------------------------------------------------------------------------- 1 | .radio-separator { 2 | margin-top: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/assets/img/app/no-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/src/assets/img/app/no-image.png -------------------------------------------------------------------------------- /src/app/features/activities/models/account-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface AccountLightweight { 2 | id: string; 3 | name: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/features/deals/models/sales-type.ts: -------------------------------------------------------------------------------- 1 | export interface SalesType { 2 | id: string; 3 | code: string; 4 | name: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/features/user/email/models/token-request.ts: -------------------------------------------------------------------------------- 1 | export interface TokenRequest { 2 | code: string; 3 | redirectUrl: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/app/features/ui/model/uploaded-image.ts: -------------------------------------------------------------------------------- 1 | export interface UploadedImage { 2 | file: File; 3 | height: number; 4 | width: number; 5 | } -------------------------------------------------------------------------------- /src/assets/img/vector/office-env2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/src/assets/img/vector/office-env2.jpg -------------------------------------------------------------------------------- /src/app/features/user/email/models/google-api-error.ts: -------------------------------------------------------------------------------- 1 | export interface GoogleApiError { 2 | error: string; 3 | erroDescription: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/assets/img/api/gmail/Gmail-Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/src/assets/img/api/gmail/Gmail-Logo.png -------------------------------------------------------------------------------- /src/app/features/activities/models/activity-outcome.ts: -------------------------------------------------------------------------------- 1 | export interface ActivityOutcome { 2 | id: string; 3 | name: string; 4 | sentiment: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/features/deals/models/account-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface AccountLightweight { 2 | id: string; 3 | name: string; 4 | country: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/features/deals/models/deal-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface DealLightweight { 2 | id: string; 3 | name: string; 4 | description: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/app/features/deals/models/deal-stage.ts: -------------------------------------------------------------------------------- 1 | export interface DealStage { 2 | id: string; 3 | salesType: string; 4 | index: number; 5 | name: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/img/logo/careydevelopment-logo-sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/careydevelopment/careydevelopmentcrm/HEAD/src/assets/img/logo/careydevelopment-logo-sm.png -------------------------------------------------------------------------------- /src/app/features/deals/models/price.ts: -------------------------------------------------------------------------------- 1 | export interface Price { 2 | priceType: string; 3 | unitType: string; 4 | currencyType: string; 5 | amount: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.ts: -------------------------------------------------------------------------------- 1 | export interface Breadcrumb { 2 | name: string; 3 | url: string; 4 | queryParams?: any; 5 | pauseDisplay?: boolean; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/ui/confirmation-dialog/confirmation-dialog.ts: -------------------------------------------------------------------------------- 1 | export class ConfirmationDialogModel { 2 | constructor(public title: string, public message: string) { } 3 | } 4 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-redirect/email-redirect.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | baseUserServiceUrl: 'http://remotehost', 4 | baseGeoServiceUrl: 'http://remotehost' 5 | }; 6 | -------------------------------------------------------------------------------- /src/app/features/deals/models/sales-owner-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface SalesOwnerLightweight { 2 | id: string; 3 | firstName: string; 4 | lastName: string; 5 | username: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/features/activities/models/sales-owner-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface SalesOwnerLightweight { 2 | id: string; 3 | firstName: string; 4 | lastName: string; 5 | username: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/app/features/ui/model/nav-item.ts: -------------------------------------------------------------------------------- 1 | export interface NavItem { 2 | displayName: string; 3 | disabled?: boolean; 4 | iconName: string; 5 | route?: string; 6 | children?: NavItem[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/models/sales-owner.ts: -------------------------------------------------------------------------------- 1 | export interface SalesOwner { 2 | id: string; 3 | firstName: string; 4 | lastName: string; 5 | email: string; 6 | username: string; 7 | phoneNumber: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/app/models/address.ts: -------------------------------------------------------------------------------- 1 | export interface Address { 2 | street1: string; 3 | street2: string; 4 | city: string; 5 | state: string; 6 | zip: string; 7 | country: string; 8 | addressType: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/features/activities/models/activity-type-lightweight.ts: -------------------------------------------------------------------------------- 1 | export interface ActivityTypeLightweight { 2 | id: string; 3 | name: string; 4 | icon: string; 5 | activityTypeCreator: string; 6 | usesStatus: boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/app/features/deals/edit-deal/edit-deal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/app/features/deals/add-deal/add-deal.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/testing/activity-helper.ts: -------------------------------------------------------------------------------- 1 | import { Activity } from "../app/features/activities/models/activity"; 2 | 3 | export const goodActivity = { 4 | id: '3', 5 | startDate: 1615547700000, 6 | endDate: 1615551300000 7 | } as Activity 8 | 9 | -------------------------------------------------------------------------------- /src/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "exclude": [ 8 | "test.ts", 9 | "**/*.spec.ts" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/app/models/address-type.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from "./name-value-map"; 2 | 3 | export const addressTypes: DisplayValueMap[] = 4 | [ 5 | { display: 'Home', value: 'HOME' }, 6 | { display: 'Work', value: 'WORK' } 7 | ]; 8 | -------------------------------------------------------------------------------- /src/app/features/accounts/edit-account/edit-account.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/app/features/contacts/edit-contact/edit-contact.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/app/features/user/email/models/email.ts: -------------------------------------------------------------------------------- 1 | export interface Email { 2 | html: string; 3 | plainText: string; 4 | subject: string; 5 | date: number; 6 | from: string; 7 | to: string; 8 | id: string; 9 | snippet: string; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/features/activities/add-activity/add-activity.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/app/features/activities/edit-activity/edit-activity.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | -------------------------------------------------------------------------------- /src/app/features/deals/constants/unit-type.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const unitTypes: DisplayValueMap[] = 4 | [ 5 | { display: 'Hour', value: 'HOUR' }, 6 | { display: 'Day', value: 'DAY' } 7 | ]; 8 | -------------------------------------------------------------------------------- /src/app/models/phone-type.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from "./name-value-map"; 2 | 3 | export const phoneTypes: DisplayValueMap[] = [ 4 | { display: 'Home', value: 'HOME' }, 5 | { display: 'Work', value: 'WORK' }, 6 | { display: 'Cell', value: 'CELL' } 7 | ]; 8 | -------------------------------------------------------------------------------- /src/app/features/deals/models/product.ts: -------------------------------------------------------------------------------- 1 | import { Price } from './price'; 2 | 3 | export interface Product { 4 | id: string; 5 | name: string; 6 | description: string; 7 | lineOfBusiness: string; 8 | productType: string; 9 | prices: Price[]; 10 | } 11 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/features/deals/deals-by-contact/deals-by-contact.component.css: -------------------------------------------------------------------------------- 1 | mat-line { 2 | height: 22px; 3 | } 4 | 5 | .small-icon { 6 | height: 16px !important; 7 | width: 16px !important; 8 | font-size: 16px !important; 9 | line-height: 16px !important; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/features/deals/models/deal-criteria.ts: -------------------------------------------------------------------------------- 1 | export class DealCriteria { 2 | userId: string; 3 | orderBy: string = 'expectedClosureDate'; 4 | orderType: string = 'DESC'; 5 | maxResults: number = 20; 6 | minDate: number = 0; 7 | maxDate: number = 20000000000000; 8 | } 9 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /src/app/util/ui-util.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class UiUtil { 6 | 7 | scrollToTop() { 8 | const element = document.querySelector('mat-sidenav-content') || window; 9 | element.scrollTo(0, 0); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/features/activities/recent-activities-by-contact/recent-activities-by-contact.component.css: -------------------------------------------------------------------------------- 1 | mat-line { 2 | height:22px; 3 | } 4 | 5 | .small-icon { 6 | height: 16px !important; 7 | width: 16px !important; 8 | font-size: 16px !important; 9 | line-height: 16px !important; 10 | } 11 | 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/app/directives/directives.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PhoneMaskDirective } from '../util/phone-mask.directive'; 3 | 4 | @NgModule({ 5 | declarations: [ 6 | PhoneMaskDirective 7 | ], 8 | exports: [ 9 | PhoneMaskDirective 10 | ] 11 | }) 12 | export class DirectivesModule { } 13 | -------------------------------------------------------------------------------- /src/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "test.ts", 12 | "polyfills.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/app/features/deals/constants/price-type.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const priceTypes: DisplayValueMap[] = 4 | [ 5 | { display: 'Single Item', value: 'SINGLE_ITEM' }, 6 | { display: 'Per Unit', value: 'PER_UNIT' }, 7 | { display: 'Flat Rate', value: 'FLAT_RATE' } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/login/login.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | 4 | @Component({ 5 | selector: 'app-login', 6 | templateUrl: './login.component.html', 7 | styleUrls: ['./login.component.css'] 8 | }) 9 | export class LoginComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit() { } 14 | } 15 | -------------------------------------------------------------------------------- /src/app/services/string.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class StringService { 6 | 7 | removeInvisibleCharacters(str: string): string { 8 | let result: string = str.replace(/[\p{Cf}]/gu, ''); 9 | result = result.trim(); 10 | 11 | return result; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/app/features/activities/models/activity-search-criteria.ts: -------------------------------------------------------------------------------- 1 | export class ActivitySearchCriteria { 2 | dealId: string = ''; 3 | contactId: string = ''; 4 | minDate: number = 0; 5 | maxDate: number = 20000000000000; 6 | orderBy: string = 'startDate'; 7 | orderType: string = 'DESC'; 8 | maxResults: number = 50; 9 | salesOwnerId: string = ''; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/features/activities/constants/activity-status.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const activityStatuses: DisplayValueMap[] = [ 4 | { display: 'Pending', value: 'PENDING' }, 5 | { display: 'On Hold', value: 'ON_HOLD' }, 6 | { display: 'Cancelled', value: 'CANCELLED' }, 7 | { display: 'Completed', value: 'COMPLETED' } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/features/deals/models/contact-lightweight.ts: -------------------------------------------------------------------------------- 1 | import { AccountLightweight } from './account-lightweight'; 2 | import { SalesOwnerLightweight } from './sales-owner-lightweight'; 3 | 4 | export interface ContactLightweight { 5 | id: string; 6 | firstName: string; 7 | lastName: string; 8 | account: AccountLightweight; 9 | salesOwner: SalesOwnerLightweight; 10 | } 11 | -------------------------------------------------------------------------------- /src/app/ui/confirmation-dialog/confirmation-dialog.component.html: -------------------------------------------------------------------------------- 1 |

2 | {{title}} 3 |

4 | 5 |
6 |

{{message}}

7 |
8 | 9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /src/app/features/activities/models/contact-lightweight.ts: -------------------------------------------------------------------------------- 1 | import { AccountLightweight } from './account-lightweight'; 2 | import { SalesOwnerLightweight } from './sales-owner-lightweight'; 3 | 4 | export interface ContactLightweight { 5 | id: string; 6 | firstName: string; 7 | lastName: string; 8 | account: AccountLightweight; 9 | salesOwner: SalesOwnerLightweight; 10 | } 11 | -------------------------------------------------------------------------------- /.vs/VSWorkspaceState.json: -------------------------------------------------------------------------------- 1 | { 2 | "ExpandedNodes": [ 3 | "", 4 | "\\src", 5 | "\\src\\app", 6 | "\\src\\app\\features\\accounts", 7 | "\\src\\app\\features\\accounts\\view-accounts", 8 | "\\src\\app\\features\\contacts", 9 | "\\src\\app\\features\\contacts\\view-contacts" 10 | ], 11 | "SelectedNode": "\\README.md", 12 | "PreviewInSolutionExplorer": false 13 | } -------------------------------------------------------------------------------- /src/app/features/activities/models/activity-type.ts: -------------------------------------------------------------------------------- 1 | import { ActivityOutcome } from "./activity-outcome"; 2 | import { ActivityTypeLightweight } from "./activity-type-lightweight"; 3 | 4 | export interface ActivityType extends ActivityTypeLightweight { 5 | requiresOutcome: boolean; 6 | usesLocation: boolean; 7 | possibleOutcomes: ActivityOutcome[]; 8 | usesEndDate: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/app/features/contacts/constants/line-of-business.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const linesOfBusiness: DisplayValueMap[] = [ 4 | { display: 'Java Enterprise', value: 'JAVA_ENTERPRISE' }, 5 | { display: 'Angular', value: 'ANGULAR' }, 6 | { display: 'Dev Ops', value: 'DEV_OPS' }, 7 | { display: 'Full Stack', value: 'FULL_STACK' } 8 | ]; 9 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home', 5 | templateUrl: './home.component.html', 6 | styleUrls: ['./home.component.css'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | export class HomeComponent implements OnInit { 10 | 11 | ngOnInit() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | 3 | describe('workspace-project App', () => { 4 | let page: AppPage; 5 | 6 | beforeEach(() => { 7 | page = new AppPage(); 8 | }); 9 | 10 | it('should display welcome message', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('Welcome to careydevelopmentcrm!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/testing/contact-helper.ts: -------------------------------------------------------------------------------- 1 | import { Contact } from "../app/features/contacts/models/contact"; 2 | 3 | export const goodContact = { 4 | id: '1' 5 | } as Contact 6 | 7 | export const goodContact2 = { 8 | id: '2' 9 | } as Contact 10 | 11 | export const goodContact3 = { 12 | id: '3' 13 | } as Contact 14 | 15 | export const goodContacts = [goodContact, goodContact2, goodContact3] as Contact[]; 16 | -------------------------------------------------------------------------------- /src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/app/features/accounts/add-account/add-account.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-add-account', 5 | templateUrl: './add-account.component.html', 6 | styleUrls: ['./add-account.component.css'] 7 | }) 8 | export class AddAccountComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/features/contacts/add-contact/add-contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-add-contact', 5 | templateUrl: './add-contact.component.html', 6 | styleUrls: ['./add-contact.component.css'] 7 | }) 8 | export class AddContactComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/features/ui/menu-list-item/menu-list-item.component.css: -------------------------------------------------------------------------------- 1 | .twistie-separator { 2 | flex: 1 1 0%; 3 | } 4 | 5 | .routeIcon { 6 | margin-right: 10px; 7 | font-size: 16pt; 8 | } 9 | 10 | .menu-item-text { 11 | font-size: 10pt; 12 | } 13 | 14 | .menu-twistie { 15 | font-size: 10pt; 16 | height: 12px; 17 | width: 12px; 18 | } 19 | 20 | .mat-list-item.active { 21 | background-color: #e8eaf6; 22 | } 23 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | baseUserServiceUrl: 'http://localhost', 8 | baseGeoServiceUrl: 'http://localhost' 9 | }; 10 | -------------------------------------------------------------------------------- /src/app/directives/single-class-base.directive.ts: -------------------------------------------------------------------------------- 1 | import { HostBinding, Input } from "@angular/core"; 2 | 3 | export abstract class SingleClassBaseDirective { 4 | 5 | protected _elementClass: string = ""; 6 | 7 | @Input('class') 8 | @HostBinding('class') 9 | get elementClass(): string { 10 | return this._elementClass; 11 | } 12 | 13 | set(val: string) { 14 | this._elementClass = val; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Observable, Subject } from 'rxjs'; 3 | 4 | @Injectable() 5 | export class BreadcrumbService { 6 | private subject = new Subject(); 7 | 8 | onUpdate(): Observable { 9 | return this.subject.asObservable(); 10 | } 11 | 12 | updateBreadcrumb(str: string) { 13 | this.subject.next(str); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/app/util/nosanitizepipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Pipe({ name: 'noSanitize' }) 5 | export class NoSanitizePipe implements PipeTransform { 6 | constructor(private domSanitizer: DomSanitizer) { } 7 | 8 | transform(html: string): SafeHtml { 9 | return this.domSanitizer.bypassSecurityTrustHtml(html); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/app/features/contacts/constants/contact-status.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const contactStatuses: DisplayValueMap[] = [ 4 | { display: 'New', value: 'NEW' }, 5 | { display: 'Contacted', value: 'CONTACTED' }, 6 | { display: 'Interested', value: 'INTERESTED' }, 7 | { display: 'Under Review', value: 'UNDER_REVIEW' }, 8 | { display: 'Customer', value: 'CUSTOMER' } 9 | ]; 10 | -------------------------------------------------------------------------------- /src/browserslist: -------------------------------------------------------------------------------- 1 | # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | # 5 | # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed 6 | 7 | > 0.5% 8 | last 2 versions 9 | Firefox ESR 10 | not dead 11 | not IE 9-11 -------------------------------------------------------------------------------- /src/app/features/deals/models/deal.ts: -------------------------------------------------------------------------------- 1 | import { ContactLightweight } from './contact-lightweight'; 2 | import { Product } from './product'; 3 | import { DealStage } from './deal-stage'; 4 | import { DealLightweight } from './deal-lightweight'; 5 | 6 | export interface Deal extends DealLightweight { 7 | product: Product; 8 | contact: ContactLightweight; 9 | units: number; 10 | stage: DealStage; 11 | expectedClosureDate: number; 12 | } 13 | -------------------------------------------------------------------------------- /src/app/directives/multiple-classes-base.directive.ts: -------------------------------------------------------------------------------- 1 | import { HostBinding, Input } from "@angular/core"; 2 | 3 | export abstract class MultipleClassesBaseDirective { 4 | 5 | protected _elementClass: string[] = []; 6 | 7 | @Input('class') 8 | @HostBinding('class') 9 | get elementClass(): string { 10 | return this._elementClass.join(' '); 11 | } 12 | 13 | set(val: string) { 14 | this._elementClass = val.split(' '); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/app/features/dashboard/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-dashboard', 5 | encapsulation: ViewEncapsulation.None, 6 | templateUrl: './dashboard.component.html', 7 | styleUrls: ['./dashboard.component.css'] 8 | }) 9 | export class DashboardComponent implements OnInit { 10 | 11 | constructor() { } 12 | 13 | ngOnInit(): void { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/app/features/deals/view-deals/view-deals.component.css: -------------------------------------------------------------------------------- 1 | table { 2 | min-width: 1150px; 3 | max-width: 100%; 4 | } 5 | 6 | .table-container { 7 | width: 100%; 8 | max-width: 100%; 9 | overflow-x: auto; 10 | overflow-y: hidden; 11 | } 12 | 13 | .mat-row:nth-child(even) { 14 | background-color: #F8F8F8; 15 | } 16 | 17 | .mat-row:nth-child(odd) { 18 | background-color: #FFFFFF; 19 | } 20 | 21 | .mat-row:hover { 22 | background-color: #E8EAF6; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/features/accounts/constants/account-status.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const accountStatuses: DisplayValueMap[] = [ 4 | { display: 'New', value: 'NEW' }, 5 | { display: 'Active', value: 'ACTIVE' }, 6 | { display: 'Inactive', value: 'INACTIVE' }, 7 | { display: 'On Hold', value: 'ON_HOLD' }, 8 | { display: 'Customer', value: 'CUSTOMER' }, 9 | { display: 'Terminated', value: 'TERMINATED' } 10 | ]; 11 | -------------------------------------------------------------------------------- /src/app/features/accounts/view-accounts/view-accounts.component.css: -------------------------------------------------------------------------------- 1 | table { 2 | min-width: 1050px; 3 | max-width: 100%; 4 | } 5 | 6 | .table-container { 7 | width: 100%; 8 | max-width: 100%; 9 | overflow-x: auto; 10 | overflow-y: hidden; 11 | } 12 | 13 | .mat-row:nth-child(even) { 14 | background-color: #F8F8F8; 15 | } 16 | 17 | .mat-row:nth-child(odd) { 18 | background-color: #FFFFFF; 19 | } 20 | 21 | .mat-row:hover { 22 | background-color: #E8EAF6; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { BreadcrumbComponent } from './breadcrumb.component'; 4 | import { BreadcrumbService } from './breadcrumb.service'; 5 | 6 | @NgModule({ 7 | imports: [CommonModule], 8 | declarations: [BreadcrumbComponent], 9 | exports: [BreadcrumbComponent], 10 | providers: [BreadcrumbService] 11 | }) 12 | export class BreadcrumbModule { } 13 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activities/view-activities.component.css: -------------------------------------------------------------------------------- 1 | table { 2 | min-width: 1450px; 3 | max-width: 100%; 4 | } 5 | 6 | .table-container { 7 | width: 100%; 8 | max-width: 100%; 9 | overflow-x: auto; 10 | overflow-y: hidden; 11 | } 12 | 13 | .mat-row:nth-child(even) { 14 | background-color: #F8F8F8; 15 | } 16 | 17 | .mat-row:nth-child(odd) { 18 | background-color: #FFFFFF; 19 | } 20 | 21 | .mat-row:hover { 22 | background-color: #E8EAF6; 23 | } 24 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contacts/view-contacts.component.css: -------------------------------------------------------------------------------- 1 | table { 2 | min-width: 1150px; 3 | max-width: 100%; 4 | } 5 | 6 | .table-container { 7 | width: 100%; 8 | max-width: 100%; 9 | overflow-x: auto; 10 | overflow-y: hidden; 11 | } 12 | 13 | .mat-row:nth-child(even) { 14 | background-color: #F8F8F8; 15 | } 16 | 17 | .mat-row:nth-child(odd) { 18 | background-color: #FFFFFF; 19 | } 20 | 21 | .mat-row:hover { 22 | background-color: #E8EAF6; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/app/models/source.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from "./name-value-map"; 2 | 3 | export const sources: DisplayValueMap[] = [ 4 | { display: 'Referral', value: 'REFERRAL' }, 5 | { display: 'Email', value: 'EMAIL' }, 6 | { display: 'Inbound Call', value: 'INBOUND_SALES_CALL' }, 7 | { display: 'Website Form', value: 'WEBSITE_FORM' }, 8 | { display: 'Walk-In', value: 'WALKIN' }, 9 | { display: 'Chatbot', value: 'CHATBOT' }, 10 | { display: 'Other', value: 'OTHER' } 11 | ]; 12 | -------------------------------------------------------------------------------- /src/app/services/currency.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DatePipe } from '@angular/common'; 3 | 4 | const pipe = new DatePipe('en-US'); 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class CurrencyService { 8 | 9 | constructor() { } 10 | 11 | formatForDollars(cents: number): number { 12 | let dollars: number = 0; 13 | 14 | if (cents) { 15 | dollars = cents / 100; 16 | } 17 | 18 | return dollars; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/app/features/deals/view-deal/view-deal.component.css: -------------------------------------------------------------------------------- 1 | .deal-label { 2 | font-weight: 500; 3 | } 4 | 5 | .subtitle { 6 | font-weight: 500; 7 | font-size: 14pt; 8 | } 9 | 10 | .deal-stage { 11 | margin-top: 10px; 12 | font-weight: bold; 13 | font-size: 14pt; 14 | color: #3F51B5; 15 | } 16 | 17 | .won-button { 18 | background-color: green !important; 19 | color: #ffffff; 20 | } 21 | 22 | .lost-button { 23 | background-color: red !important; 24 | color: #ffffff; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/app/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HomeComponent } from './home.component'; 4 | import { RouterModule } from '@angular/router'; 5 | 6 | 7 | export const routes = [ 8 | { path: '', component: HomeComponent } 9 | ]; 10 | 11 | @NgModule({ 12 | declarations: [HomeComponent], 13 | imports: [ 14 | CommonModule, 15 | RouterModule.forChild(routes) 16 | ] 17 | }) 18 | export class HomeModule { } 19 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.component.html: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 |
12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "emitDecoratorMetadata": true, 11 | "experimentalDecorators": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "node_modules/@types" 15 | ], 16 | "lib": [ 17 | "es2018", 18 | "dom" 19 | ] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/features/features.component.css: -------------------------------------------------------------------------------- 1 | mat-sidenav-container, mat-sidenav-content, mat-sidenav { 2 | /* Using 100vh causes double scrollbars to appear on some devices */ 3 | height: 90vh; 4 | } 5 | 6 | mat-sidenav-container { 7 | padding-bottom: 10px; 8 | border-bottom: 1px solid #808080; 9 | } 10 | 11 | .mat-sidenav { 12 | width: 220px; 13 | } 14 | 15 | button.menu-button { 16 | color: inherit; 17 | background: transparent; 18 | cursor: pointer; 19 | border: none; 20 | outline: none; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/app/pipes/no-value-display.pipe.ts: -------------------------------------------------------------------------------- 1 | import { P } from '@angular/cdk/keycodes'; 2 | import { Pipe, PipeTransform } from '@angular/core'; 3 | 4 | /** 5 | * This pipe displays a "no value entered" message when there's no value for a particular 6 | * form field. 7 | * */ 8 | @Pipe({ 9 | name: 'noValueDisplay' 10 | }) 11 | export class NoValueDisplayPipe implements PipeTransform { 12 | transform(value: string): string { 13 | 14 | if (!value) { 15 | return "(No value entered)"; 16 | } else return value; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { LogoutService } from './services/logout.service'; 3 | 4 | @Component({ 5 | selector: 'app-root', 6 | templateUrl: './app.component.html', 7 | styleUrls: ['./app.component.css'] 8 | }) 9 | export class AppComponent { 10 | 11 | title = 'Carey Development CRM'; 12 | 13 | //instantiating LogoutService at startup 14 | //yes I could also do this in the module with APP_INITIALIZER, but... why? 15 | constructor(private logoutService: LogoutService) { } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/app/features/accounts/models/account.ts: -------------------------------------------------------------------------------- 1 | import { Address } from "../../../models/address"; 2 | import { Phone } from "../../../models/phone"; 3 | import { SalesOwner } from "../../../models/sales-owner"; 4 | 5 | 6 | export interface Account { 7 | id: string; 8 | name: string; 9 | address: Address; 10 | phone: Phone; 11 | industry: string; 12 | description: string; 13 | numberOfEmployees: number; 14 | stockSymbol: string; 15 | annualRevenue: number; 16 | status: string; 17 | source: string; 18 | salesOwner: SalesOwner; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phones-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{phoneType.display}} Phone 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/app/features/deals/stage-progress-bar/stage-progress-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 8 | {{stage.name}} 9 | 10 |
  • 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/ui/service/display-map.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { DisplayValueMap } from '../../../models/name-value-map'; 3 | 4 | @Injectable({ providedIn: 'root' }) 5 | export class DisplayValueMapService { 6 | 7 | constructor() { } 8 | 9 | getDisplay(value: string, list: DisplayValueMap[]): string { 10 | let display = ''; 11 | 12 | for (let i = 0; i < list.length; i++) { 13 | if (list[i].value == value) { 14 | display = list[i].display; 15 | break; 16 | } 17 | } 18 | 19 | return display; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/future-pipeline/future-pipeline.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
{{pageTitle}}
8 |
{{pageSubtitle}}
9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-choice/email-choice.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Choose Your Email Service

6 |
7 |
8 | 9 |
10 |
11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /src/app/features/activities/ui/update-notes-dialog/update-notes-dialog.html: -------------------------------------------------------------------------------- 1 |

Add Notes

2 |
3 |
4 | 5 | Notes 6 | 7 | 8 |
9 |
10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/accounts-ranked/accounts-ranked.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
{{pageTitle}}
8 |
{{pageSubtitle}}
9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/contacts/status-progress-bar/status-progress-bar.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • 8 | {{status.display}} 9 | 10 |
  • 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/deal-share-by-contact/deal-share-by-contact.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
{{pageTitle}}
8 |
{{pageSubtitle}}
9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/revenue-contribution/revenue-contribution.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 |
7 |
{{pageTitle}}
8 |
{{pageSubtitle}}
9 |
10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/features/activities/models/activity.ts: -------------------------------------------------------------------------------- 1 | import { ContactLightweight } from "./contact-lightweight"; 2 | import { ActivityOutcome } from "./activity-outcome"; 3 | import { ActivityTypeLightweight } from "./activity-type-lightweight"; 4 | import { DealLightweight } from "../../deals/models/deal-lightweight"; 5 | 6 | export interface Activity { 7 | id: string; 8 | title: string; 9 | type: ActivityTypeLightweight; 10 | outcome: ActivityOutcome; 11 | notes: string; 12 | location: string; 13 | startDate: number; 14 | endDate: number; 15 | contact: ContactLightweight; 16 | deal: DealLightweight; 17 | status: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/app/features/dashboard/dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
 
11 |
12 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/addresses-form.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{addressType.display}} Address 6 | 7 | 8 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /src/app/features/features.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | CRM Application 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Carey Development CRM 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact-menu/view-contact-menu.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/features/deals/service/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { environment } from '../../../../environments/environment'; 5 | import { Product } from '../models/product'; 6 | 7 | 8 | const baseUrl: string = environment.baseProductServiceUrl; 9 | 10 | @Injectable({ providedIn: 'root' }) 11 | export class ProductService { 12 | 13 | constructor(private http: HttpClient) { } 14 | 15 | fetchAllProducts(): Observable { 16 | let url = `${baseUrl}/products`; 17 | console.log("Fetch products URL is " + url); 18 | 19 | return this.http.get(url); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone-testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting() 16 | ); 17 | // Then we find all the tests. 18 | const context = require.context('./', true, /\.spec\.ts$/); 19 | // And load the modules. 20 | context.keys().map(context); 21 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activity/view-activity.component.css: -------------------------------------------------------------------------------- 1 | .vertical-layout { 2 | margin-bottom: 15px !important; 3 | } 4 | 5 | .empty-set-status-message { 6 | font-size: 14pt; 7 | font-weight: 400; 8 | } 9 | 10 | .normal-detail { 11 | font-size: 12pt; 12 | font-weight: 500; 13 | } 14 | 15 | .column-header { 16 | font-size: 12pt; 17 | font-weight: 500; 18 | text-decoration: underline; 19 | margin-bottom: 5px; 20 | } 21 | 22 | .column-layout { 23 | margin-bottom: 20px; 24 | margin-right: 80px; 25 | } 26 | 27 | .normal-status { 28 | font-size: 14pt; 29 | color: green; 30 | } 31 | 32 | .free-text { 33 | font-size: 11pt; 34 | font-weight: 300; 35 | } 36 | 37 | .labeled-line-item { 38 | margin-top: 10px; 39 | } 40 | -------------------------------------------------------------------------------- /src/app/config/validation/field-summaries.ts: -------------------------------------------------------------------------------- 1 | import { ErrorFieldMessage } from "carey-validation"; 2 | 3 | /** 4 | * Additional summary messages identified per field. 5 | * 6 | * These are the messages that usually appear at the bottom of 7 | * an invalid form in list item format. 8 | * 9 | * These are in addition to the standard summary messages identified 10 | * in allFieldMessages in carey-validation. 11 | * */ 12 | export const allFieldSummaries: ErrorFieldMessage[] = [ 13 | { 14 | field: "source", 15 | message: "Please enter a valid source" 16 | }, 17 | { 18 | field: "status", 19 | message: "Please enter a valid status" 20 | }, 21 | { 22 | field: "account", 23 | message: "Please enter a valid account" 24 | } 25 | ]; 26 | -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | 5 | describe('HomeComponent', () => { 6 | let component: HomeComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(waitForAsync(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [ HomeComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomeComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/login/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { LoginComponent } from './login.component'; 3 | import { RouterModule } from '@angular/router'; 4 | import { FlexLayoutModule } from '@angular/flex-layout'; 5 | import { MatIconModule } from '@angular/material/icon'; 6 | import { CommonModule } from '@angular/common'; 7 | import { AuthModule } from 'carey-auth'; 8 | 9 | export const routes = [ 10 | { path: '', component: LoginComponent } 11 | ]; 12 | 13 | 14 | @NgModule({ 15 | declarations: [ 16 | LoginComponent 17 | ], 18 | imports: [ 19 | CommonModule, 20 | FlexLayoutModule, 21 | MatIconModule, 22 | AuthModule, 23 | RouterModule.forChild(routes) 24 | ] 25 | }) 26 | export class LoginModule { } 27 | -------------------------------------------------------------------------------- /src/app/features/user/email/inbox/inbox.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { InboxComponent } from './inbox.component'; 4 | 5 | describe('InboxComponent', () => { 6 | let component: InboxComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ InboxComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(InboxComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/features.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FeaturesComponent } from './features.component'; 4 | 5 | describe('FeaturesComponent', () => { 6 | let component: FeaturesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FeaturesComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FeaturesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/user/email/message/message.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MessageComponent } from './message.component'; 4 | 5 | describe('MessageComponent', () => { 6 | let component: MessageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ MessageComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MessageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/dashboard/dashboard.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DashboardComponent } from './dashboard.component'; 4 | 5 | describe('DashboardComponent', () => { 6 | let component: DashboardComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ DashboardComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DashboardComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/deals/view-deal/view-deal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewDealComponent } from './view-deal.component'; 4 | 5 | describe('ViewDealComponent', () => { 6 | let component: ViewDealComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewDealComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewDealComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/deals/view-deals/view-deals.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewDealsComponent } from './view-deals.component'; 4 | 5 | describe('ViewDealsComponent', () => { 6 | let component: ViewDealsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewDealsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewDealsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/accounts/add-account/add-account.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddAccountComponent } from './add-account.component'; 4 | 5 | describe('AddAccountComponent', () => { 6 | let component: AddAccountComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AddAccountComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddAccountComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/contacts/add-contact/add-contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AddContactComponent } from './add-contact.component'; 4 | 5 | describe('AddContactComponent', () => { 6 | let component: AddContactComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AddContactComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AddContactComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/accounts/account-form/account-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AccountFormComponent } from './account-form.component'; 4 | 5 | describe('AccountFormComponent', () => { 6 | let component: AccountFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AccountFormComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AccountFormComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/accounts/edit-account/edit-account.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EditAccountComponent } from './edit-account.component'; 4 | 5 | describe('EditAccountComponent', () => { 6 | let component: EditAccountComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ EditAccountComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EditAccountComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/accounts/view-account/view-account.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewAccountComponent } from './view-account.component'; 4 | 5 | describe('ViewAccountComponent', () => { 6 | let component: ViewAccountComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewAccountComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewAccountComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-choice/email-choice.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmailChoiceComponent } from './email-choice.component'; 4 | 5 | describe('EmailChoiceComponent', () => { 6 | let component: EmailChoiceComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ EmailChoiceComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmailChoiceComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/accounts/view-accounts/view-accounts.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewAccountsComponent } from './view-accounts.component'; 4 | 5 | describe('ViewAccountsComponent', () => { 6 | let component: ViewAccountsComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewAccountsComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewAccountsComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activity/view-activity.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewActivityComponent } from './view-activity.component'; 4 | 5 | describe('ViewActivityComponent', () => { 6 | let component: ViewActivityComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewActivityComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewActivityComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/user/email/compose-email/compose-email.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ComposeEmailComponent } from './compose-email.component'; 4 | 5 | describe('ComposeEmailComponent', () => { 6 | let component: ComposeEmailComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ComposeEmailComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ComposeEmailComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-redirect/email-redirect.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { EmailRedirectComponent } from './email-redirect.component'; 4 | 5 | describe('EmailRedirectComponent', () => { 6 | let component: EmailRedirectComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ EmailRedirectComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(EmailRedirectComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, waitForAsync } from '@angular/core/testing'; 2 | import { AppComponent } from './app.component'; 3 | 4 | describe('AppComponent', () => { 5 | beforeEach(waitForAsync(() => { 6 | TestBed.configureTestingModule({ 7 | declarations: [ 8 | AppComponent 9 | ], 10 | }).compileComponents(); 11 | })); 12 | 13 | it('should create the app', () => { 14 | const fixture = TestBed.createComponent(AppComponent); 15 | const app = fixture.debugElement.componentInstance; 16 | expect(app).toBeTruthy(); 17 | }); 18 | 19 | it(`should have as title 'Carey Development CRM'`, () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.debugElement.componentInstance; 22 | expect(app.title).toEqual('Carey Development CRM'); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/features/activities/activities-list/activities-list.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ActivitiesListComponent } from './activities-list.component'; 4 | 5 | describe('ActivitiesListComponent', () => { 6 | let component: ActivitiesListComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ActivitiesListComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ActivitiesListComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activities/view-activities.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewActivitiesComponent } from './view-activities.component'; 4 | 5 | describe('ViewActivitiesComponent', () => { 6 | let component: ViewActivitiesComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ ViewActivitiesComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(ViewActivitiesComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/accounts-ranked/accounts-ranked.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { AccountsRankedComponent } from './accounts-ranked.component'; 4 | 5 | describe('AccountsRankedComponent', () => { 6 | let component: AccountsRankedComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ AccountsRankedComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(AccountsRankedComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/deals/charts/future-pipeline/future-pipeline.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FuturePipelineComponent } from './future-pipeline.component'; 4 | 5 | describe('FuturePipelineComponent', () => { 6 | let component: FuturePipelineComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FuturePipelineComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FuturePipelineComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/ui/menu-list-item/menu-list-item.component.html: -------------------------------------------------------------------------------- 1 | 4 | {{item.iconName}} 5 | {{item.displayName}} 6 | 7 | 8 | 9 | expand_more 10 | 11 | 12 | 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/app/features/deals/stage-progress-bar/stage-progress-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { StageProgressBarComponent } from './stage-progress-bar.component'; 4 | 5 | describe('StageProgressBarComponent', () => { 6 | let component: StageProgressBarComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ StageProgressBarComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(StageProgressBarComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/features/contacts/models/contact.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '../../../models/address'; 2 | import { Phone } from '../../../models/phone'; 3 | import { SalesOwner } from '../../../models/sales-owner'; 4 | import { Account } from '../../accounts/models/account'; 5 | 6 | export interface Contact { 7 | id: string; 8 | firstName: string; 9 | lastName: string; 10 | email: string; 11 | phones: Phone[]; 12 | addresses: Address[]; 13 | source: string; 14 | sourceDetails: string; 15 | linesOfBusiness: string[]; 16 | authority: boolean; 17 | title: string; 18 | status: string; 19 | statusChange: number; 20 | salesOwner: SalesOwner; 21 | account: Account; 22 | timezone: string; 23 | tags: string[]; 24 | canCall: boolean; 25 | canText: boolean; 26 | canEmail: boolean; 27 | birthdayMonth: string; 28 | birthdayDay: number; 29 | notes; 30 | } 31 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/lib/config.ts 3 | 4 | const { SpecReporter } = require('jasmine-spec-reporter'); 5 | 6 | exports.config = { 7 | allScriptsTimeout: 11000, 8 | specs: [ 9 | './src/**/*.e2e-spec.ts' 10 | ], 11 | capabilities: { 12 | 'browserName': 'chrome' 13 | }, 14 | directConnect: true, 15 | baseUrl: 'http://localhost:4200/', 16 | framework: 'jasmine', 17 | jasmineNodeOpts: { 18 | showColors: true, 19 | defaultTimeoutInterval: 30000, 20 | print: function() {} 21 | }, 22 | onPrepare() { 23 | require('ts-node').register({ 24 | project: require('path').join(__dirname, './tsconfig.e2e.json') 25 | }); 26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/app/features/deals/charts/revenue-contribution/revenue-contribution.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { RevenueContributionComponent } from './revenue-contribution.component'; 4 | 5 | describe('RevenueContributionComponent', () => { 6 | let component: RevenueContributionComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ RevenueContributionComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(RevenueContributionComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/services/logout.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { AuthenticationService } from 'carey-auth'; 3 | import { Observable } from 'rxjs'; 4 | import { EmailService } from '../features/user/email/service/email.service'; 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class LogoutService { 8 | 9 | private logoutListener$: Observable; 10 | 11 | constructor(private emailService: EmailService, private authenticationService: AuthenticationService) { 12 | this.logoutListener$ = authenticationService.authNotification$; 13 | 14 | this.logoutListener$.subscribe( 15 | (statusChange: boolean) => this.handleStatusChange(statusChange) 16 | ); 17 | } 18 | 19 | private handleStatusChange(loginStatus: boolean) { 20 | if (!loginStatus) { 21 | //user logged out 22 | this.emailService.clearObservables(); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/features/ui/service/nav.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Event, NavigationEnd, Router } from '@angular/router'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class NavService { 7 | private currentUrl = new BehaviorSubject(undefined); 8 | 9 | constructor(private router: Router) { 10 | this.router.events.subscribe((event: Event) => { 11 | if (event instanceof NavigationEnd) { 12 | this.currentUrl.next(event.urlAfterRedirects); 13 | } 14 | }); 15 | } 16 | 17 | public getCurrentUrl(): BehaviorSubject { 18 | if (!this.currentUrl.value) { 19 | //handles redirect after login 20 | let url = this.router.url; 21 | this.currentUrl.next(url); 22 | } 23 | 24 | return this.currentUrl; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/features/user/email/inbox/inbox.component.css: -------------------------------------------------------------------------------- 1 | .mat-row:nth-child(even) { 2 | background-color: #F8F8F8; 3 | } 4 | 5 | .mat-row:nth-child(odd) { 6 | background-color: #FFFFFF; 7 | } 8 | 9 | .mat-row:hover { 10 | background-color: #E8EAF6; 11 | } 12 | 13 | .checkbox-margin { 14 | margin: 0 12px; 15 | } 16 | 17 | th.mat-header-cell:first-of-type, td.mat-cell:first-of-type, td.mat-footer-cell:first-of-type { 18 | padding-left: 0px !important; 19 | } 20 | 21 | table { 22 | width: 100% 23 | } 24 | 25 | td.action-cell { 26 | width: 5%; 27 | } 28 | 29 | td.from-cell { 30 | width: 15%; 31 | padding-right: 20px; 32 | } 33 | 34 | td.date-cell { 35 | width: 10%; 36 | } 37 | 38 | td.subject-cell { 39 | white-space: nowrap; 40 | overflow: hidden; 41 | text-overflow: ellipsis; 42 | width: 70%; 43 | max-width: 0; 44 | padding-right: 20px; 45 | } 46 | 47 | .email-row:hover { 48 | cursor: pointer; 49 | } 50 | -------------------------------------------------------------------------------- /src/app/ui/confirmation-dialog/confirmation-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 3 | import { ConfirmationDialogModel } from './confirmation-dialog'; 4 | 5 | @Component({ 6 | selector: 'app-confirmation-dialog', 7 | templateUrl: './confirmation-dialog.component.html', 8 | styleUrls: ['./confirmation-dialog.component.css'] 9 | }) 10 | export class ConfirmationDialogComponent { 11 | 12 | title: string; 13 | message: string; 14 | 15 | constructor(public dialogRef: MatDialogRef, 16 | @Inject(MAT_DIALOG_DATA) public data: ConfirmationDialogModel) { 17 | this.title = data.title; 18 | this.message = data.message; 19 | } 20 | 21 | onConfirm(): void { 22 | this.dialogRef.close(true); 23 | } 24 | 25 | onDismiss(): void { 26 | this.dialogRef.close(false); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/addresses-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormBuilder } from '@angular/forms'; 3 | import { AddressesFormComponent } from './addresses-form.component'; 4 | 5 | describe('AddressesFormComponent', () => { 6 | let component: AddressesFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [AddressesFormComponent], 12 | imports: [], 13 | providers: [ 14 | FormBuilder 15 | ] 16 | }) 17 | .compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(AddressesFormComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/features/activities/ui/update-notes-dialog/update-notes-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, OnInit } from "@angular/core"; 2 | import { FormBuilder, FormGroup, Validators } from "@angular/forms"; 3 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; 4 | 5 | @Component({ 6 | selector: 'update-notes-dialog', 7 | templateUrl: 'update-notes-dialog.html', 8 | }) 9 | export class UpdateNotesDialog implements OnInit { 10 | 11 | notesFormGroup: FormGroup; 12 | 13 | constructor( 14 | public dialogRef: MatDialogRef, 15 | @Inject(MAT_DIALOG_DATA) public data: string, private fb: FormBuilder) { } 16 | 17 | ngOnInit() { 18 | this.notesFormGroup = this.fb.group({ 19 | 'notes': [this.data], 20 | }); 21 | } 22 | 23 | cancel(): void { 24 | this.dialogRef.close(null); 25 | } 26 | 27 | save(): void { 28 | let notes: string = this.notesFormGroup.controls['notes'].value; 29 | this.dialogRef.close(notes); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/app/features/user/email/message/message.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 | 10 | 13 | 16 | 17 | 18 | {{email.subject}} 19 | {{email.from}} 20 | 21 | 22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /src/app/ui/confirmation-dialog/confirmation-dialog.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; 3 | 4 | import { ConfirmationDialogComponent } from './confirmation-dialog.component'; 5 | 6 | describe('ConfirmationDialogComponent', () => { 7 | let component: ConfirmationDialogComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [ConfirmationDialogComponent], 13 | imports: [MatDialogModule], 14 | providers: [ 15 | MatDialogRef 16 | ] 17 | }) 18 | .compileComponents(); 19 | }); 20 | 21 | beforeEach(() => { 22 | fixture = TestBed.createComponent(ConfirmationDialogComponent); 23 | component = fixture.componentInstance; 24 | fixture.detectChanges(); 25 | }); 26 | 27 | it('should create', () => { 28 | expect(component).toBeTruthy(); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/review-form/review-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { Contact } from '../../models/contact'; 3 | import { ReviewFormComponent } from './review-form.component'; 4 | 5 | describe('ReviewFormComponent', () => { 6 | let component: ReviewFormComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ 12 | ReviewFormComponent 13 | ] 14 | }) 15 | .compileComponents(); 16 | }); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(ReviewFormComponent); 20 | component = fixture.componentInstance; 21 | 22 | //TODO: need to find a way to get the contact info in 23 | let contact: Contact = {} as Contact; 24 | component.contact = contact; 25 | 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/util/jwt-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { AuthenticationService } from 'carey-auth'; 5 | 6 | 7 | @Injectable() 8 | export class JwtInterceptor implements HttpInterceptor { 9 | 10 | constructor(private authenticationService: AuthenticationService) { } 11 | 12 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 13 | let token = this.authenticationService.token(); 14 | 15 | if (token) { 16 | if (this.authenticationService.isLoggedIn()) { 17 | request = this.setHeader(request, token); 18 | } 19 | } 20 | 21 | return next.handle(request); 22 | } 23 | 24 | private setHeader(request: HttpRequest, token: string): HttpRequest { 25 | request = request.clone({ 26 | setHeaders: { 27 | Authorization: `Bearer ${token}` 28 | } 29 | }); 30 | 31 | return request; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/features/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule } from '@angular/router'; 3 | import { CommonModule } from '@angular/common'; 4 | import { DashboardComponent } from './dashboard.component'; 5 | import { DealsModule } from '../deals/deals.module'; 6 | import { FlexLayoutModule } from '@angular/flex-layout'; 7 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 8 | import { MatCardModule } from '@angular/material/card'; 9 | 10 | export const routes = [ 11 | { 12 | path: '', 13 | component: DashboardComponent, 14 | pathMatch: 'full', 15 | data: { 16 | breadcrumb: 'Dashboard' 17 | } 18 | } 19 | ]; 20 | 21 | @NgModule({ 22 | declarations: [ 23 | DashboardComponent 24 | ], 25 | imports: [ 26 | CommonModule, 27 | DealsModule, 28 | FlexLayoutModule, 29 | MatProgressSpinnerModule, 30 | MatCardModule, 31 | RouterModule.forChild(routes) 32 | ] 33 | }) 34 | export class DashboardModule { 35 | constructor() { } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phones-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormBuilder } from '@angular/forms'; 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { PhonesFormComponent } from './phones-form.component'; 5 | 6 | describe('PhonesFormComponent', () => { 7 | let component: PhonesFormComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [PhonesFormComponent], 13 | imports: [ 14 | HttpClientTestingModule 15 | ], 16 | providers: [ 17 | FormBuilder 18 | ] 19 | }) 20 | .compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(PhonesFormComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-choice/email-choice.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { AlertService } from 'carey-alert'; 3 | import { EmailService } from '../service/email.service'; 4 | 5 | @Component({ 6 | selector: 'app-email-choice', 7 | templateUrl: './email-choice.component.html', 8 | styleUrls: ['./email-choice.component.css'] 9 | }) 10 | export class EmailChoiceComponent implements OnInit { 11 | 12 | constructor(private emailService: EmailService, private alertService: AlertService) { } 13 | 14 | ngOnInit(): void { 15 | } 16 | 17 | selectGmail() { 18 | this.emailService.getGoogleAuthCodeFlowUrl().subscribe( 19 | (url: string) => this.handleAuthCodeRetrieval(url), 20 | (err: Error) => this.handleAuthCodeError(err) 21 | ) 22 | } 23 | 24 | private handleAuthCodeRetrieval(url: string) { 25 | window.location.assign(url); 26 | } 27 | 28 | private handleAuthCodeError(err: Error) { 29 | console.error(err); 30 | this.alertService.error("Problem connecting with Gmail!"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, '../coverage'), 20 | reports: ['html', 'lcovonly'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false 30 | }); 31 | }; -------------------------------------------------------------------------------- /src/app/features/deals/deals-by-contact/deals-by-contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | import { DealsByContactComponent } from './deals-by-contact.component'; 6 | 7 | describe('DealsByContactComponent', () => { 8 | let component: DealsByContactComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async () => { 12 | await TestBed.configureTestingModule({ 13 | declarations: [DealsByContactComponent], 14 | imports: [ 15 | HttpClientTestingModule, 16 | RouterTestingModule 17 | ] 18 | }) 19 | .compileComponents(); 20 | }); 21 | 22 | beforeEach(() => { 23 | fixture = TestBed.createComponent(DealsByContactComponent); 24 | component = fixture.componentInstance; 25 | fixture.detectChanges(); 26 | }); 27 | 28 | it('should create', () => { 29 | expect(component).toBeTruthy(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activity/view-activity-menu/view-activity-menu.component.html: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 13 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /src/app/features/deals/add-deal/add-deal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { AlertService } from 'carey-alert'; 5 | 6 | import { AddDealComponent } from './add-deal.component'; 7 | 8 | describe('AddDealComponent', () => { 9 | let component: AddDealComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [AddDealComponent], 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientTestingModule 18 | ], 19 | providers: [AlertService] 20 | }) 21 | .compileComponents(); 22 | }); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(AddDealComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phone-type-form/phone-type-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormBuilder } from '@angular/forms'; 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { PhoneTypeFormComponent } from './phone-type-form.component'; 5 | 6 | describe('PhoneTypeFormComponent', () => { 7 | let component: PhoneTypeFormComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [PhoneTypeFormComponent], 13 | imports: [ 14 | HttpClientTestingModule 15 | ], 16 | providers: [ 17 | FormBuilder 18 | ] 19 | }) 20 | .compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(PhoneTypeFormComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/features/contacts/status-progress-bar/status-progress-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from "@angular/common/http/testing"; 3 | import { StatusProgressBarComponent } from './status-progress-bar.component'; 4 | import { AlertService } from 'carey-alert'; 5 | 6 | describe('StatusProgressBarComponent', () => { 7 | let component: StatusProgressBarComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [StatusProgressBarComponent], 13 | imports: [ 14 | HttpClientTestingModule 15 | ], 16 | providers: [ 17 | AlertService 18 | ] 19 | }) 20 | .compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(StatusProgressBarComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /src/app/features/user/profile-image/profile-image.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Profile Image

6 |

Upload a square image no smaller than 200x200. Then click the Save Image button to save it.

7 |
8 |
9 | 10 |
11 |
12 |
13 | 15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact-menu/view-contact-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { Contact } from '../../models/contact'; 4 | 5 | @Component({ 6 | selector: 'app-view-contact-menu', 7 | templateUrl: './view-contact-menu.component.html', 8 | styleUrls: ['./view-contact-menu.component.css'] 9 | }) 10 | export class ViewContactMenuComponent implements OnInit { 11 | 12 | @Input() contact: Contact; 13 | 14 | constructor(private router: Router) { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | addAppointment() { 20 | let route = '/activities/add-activity'; 21 | this.router.navigate([route], { queryParams: { contactId: this.contact.id, type: 'APPOINTMENT' } }); } 22 | 23 | addActivity() { 24 | let route = '/activities/add-activity'; 25 | this.router.navigate([route], { queryParams: { contactId: this.contact.id } }); 26 | } 27 | 28 | addDeal() { 29 | let route = '/deals/add-deal'; 30 | this.router.navigate([route], { queryParams: { contactId: this.contact.id } }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/address-type-form/address-type-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormBuilder } from '@angular/forms'; 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { AddressTypeFormComponent } from './address-type-form.component'; 5 | 6 | describe('AddressTypeFormComponent', () => { 7 | let component: AddressTypeFormComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [AddressTypeFormComponent], 13 | imports: [ 14 | HttpClientTestingModule 15 | ], 16 | providers: [ 17 | FormBuilder 18 | ] 19 | }) 20 | .compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(AddressTypeFormComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | it('should create', () => { 30 | expect(component).toBeTruthy(); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Carey Development, LLC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contacts/view-contacts.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { AlertService } from 'carey-alert'; 5 | 6 | import { ViewContactsComponent } from './view-contacts.component'; 7 | 8 | describe('ViewContactsComponent', () => { 9 | let component: ViewContactsComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [ViewContactsComponent], 15 | imports: [ 16 | HttpClientTestingModule, 17 | RouterTestingModule 18 | ], 19 | providers: [AlertService] 20 | }) 21 | .compileComponents(); 22 | }); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(ViewContactsComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/services/form.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { FormControl, FormGroup, AbstractControl } from '@angular/forms'; 3 | 4 | 5 | @Injectable({ providedIn: 'root' }) 6 | export class FormService { 7 | 8 | constructor() { } 9 | 10 | getFormErrors(form: AbstractControl) { 11 | if (form instanceof FormControl) { 12 | return form.errors ?? null; 13 | } 14 | if (form instanceof FormGroup) { 15 | const groupErrors = form.errors; 16 | const formErrors = groupErrors ? { groupErrors } : {}; 17 | Object.keys(form.controls).forEach(key => { 18 | const error = this.getFormErrors(form.get(key)); 19 | if (error !== null) { 20 | //console.log("Key " + key + " " + error); 21 | formErrors[key] = error; 22 | } 23 | }); 24 | 25 | return Object.keys(formErrors).length > 0 ? formErrors : null; 26 | } 27 | } 28 | 29 | formHasErrors(form: FormGroup): boolean { 30 | let formErrors: any = this.getFormErrors(form); 31 | 32 | let hasErrors: boolean = (formErrors && Object.keys(formErrors).length > 0); 33 | 34 | return hasErrors; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/ui/breadcrumb/breadcrumb.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | 5 | import { BreadcrumbComponent } from './breadcrumb.component'; 6 | import { BreadcrumbService } from './breadcrumb.service'; 7 | 8 | describe('BreadcrumbComponent', () => { 9 | let component: BreadcrumbComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [BreadcrumbComponent], 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientTestingModule 18 | ], 19 | providers: [ 20 | BreadcrumbService 21 | ] 22 | }) 23 | .compileComponents(); 24 | }); 25 | 26 | beforeEach(() => { 27 | fixture = TestBed.createComponent(BreadcrumbComponent); 28 | component = fixture.componentInstance; 29 | fixture.detectChanges(); 30 | }); 31 | 32 | it('should create', () => { 33 | expect(component).toBeTruthy(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact-menu/view-contact-menu.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { ViewContactMenuComponent } from './view-contact-menu.component'; 4 | import { MatMenuModule } from '@angular/material/menu'; 5 | import { ActivatedRoute } from '@angular/router'; 6 | 7 | describe('ViewContactMenuComponent', () => { 8 | let component: ViewContactMenuComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async () => { 12 | await TestBed.configureTestingModule({ 13 | declarations: [ViewContactMenuComponent], 14 | imports: [ 15 | RouterTestingModule, 16 | MatMenuModule 17 | ], 18 | providers: [ 19 | ] 20 | }) 21 | .compileComponents(); 22 | }); 23 | 24 | beforeEach(() => { 25 | fixture = TestBed.createComponent(ViewContactMenuComponent); 26 | component = fixture.componentInstance; 27 | fixture.detectChanges(); 28 | }); 29 | 30 | it('should create', () => { 31 | expect(component).toBeTruthy(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/features/deals/directives/display-stage.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostBinding, HostListener, Input, OnInit } from '@angular/core'; 2 | import { MultipleClassesBaseDirective } from '../../../directives/multiple-classes-base.directive'; 3 | import { Deal } from '../models/deal'; 4 | 5 | @Directive({ 6 | selector: '[displayStage]' 7 | }) 8 | export class DisplayStageDirective extends MultipleClassesBaseDirective implements OnInit { 9 | 10 | @Input('displayStage') deal: Deal; 11 | 12 | constructor() { 13 | super(); 14 | } 15 | 16 | ngOnInit() { 17 | console.log(this.deal); 18 | this._elementClass.push('badge'); 19 | this.addExtraClass(); 20 | } 21 | 22 | private addExtraClass() { 23 | if (this.deal && this.deal.stage) { 24 | let extraClass: string = null; 25 | let stageName: string = this.deal.stage.name; 26 | 27 | if (stageName == 'Won') extraClass = 'badge-success'; 28 | else if (stageName == 'Lost') extraClass = 'badge-error'; 29 | else if (stageName != 'Won' && stageName != 'Lost') extraClass = 'badge-info'; 30 | 31 | if (extraClass) this._elementClass.push(extraClass); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/app/features/features.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy } from '@angular/core'; 2 | import { NavItem } from './ui/model/nav-item'; 3 | import { MediaChange, MediaObserver } from "@angular/flex-layout"; 4 | import { Subscription } from 'rxjs'; 5 | import { menu } from './ui/model/menu'; 6 | 7 | @Component({ 8 | selector: 'app-features', 9 | templateUrl: './features.component.html', 10 | styleUrls: ['./features.component.css'] 11 | }) 12 | export class FeaturesComponent implements OnDestroy { 13 | 14 | private opened: boolean = true; 15 | private mediaWatcher: Subscription; 16 | private menu: NavItem[] = menu; 17 | 18 | constructor(private media: MediaObserver) { 19 | this.mediaWatcher = this.media.media$.subscribe((mediaChange: MediaChange) => { 20 | this.handleMediaChange(mediaChange); 21 | }) 22 | } 23 | 24 | private handleMediaChange(mediaChange: MediaChange) { 25 | if (this.media.isActive('lt-md')) { 26 | this.opened = false; 27 | } else { 28 | this.opened = true; 29 | } 30 | } 31 | 32 | ngOnDestroy() { 33 | this.mediaWatcher.unsubscribe(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/login/login.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; 3 | import { FormBuilder } from '@angular/forms'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { AlertService } from 'carey-alert'; 6 | 7 | import { LoginComponent } from './login.component'; 8 | 9 | describe('LoginComponent', () => { 10 | let component: LoginComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(waitForAsync(() => { 14 | TestBed.configureTestingModule({ 15 | declarations: [LoginComponent], 16 | imports: [ 17 | HttpClientTestingModule, 18 | RouterTestingModule 19 | ], 20 | providers: [ 21 | FormBuilder, 22 | AlertService 23 | ] 24 | }) 25 | .compileComponents(); 26 | })); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(LoginComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/basic-info-form/basic-info-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { FormBuilder } from '@angular/forms'; 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { BasicInfoFormComponent } from './basic-info-form.component'; 5 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 6 | 7 | describe('BasicInfoFormComponent', () => { 8 | let component: BasicInfoFormComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async () => { 12 | await TestBed.configureTestingModule({ 13 | declarations: [BasicInfoFormComponent], 14 | imports: [ 15 | HttpClientTestingModule, 16 | MatAutocompleteModule 17 | ], 18 | providers: [ 19 | FormBuilder 20 | ] 21 | }) 22 | .compileComponents(); 23 | }); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(BasicInfoFormComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/features/deals/deal-form/deal-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { FormBuilder } from '@angular/forms'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { AlertService } from 'carey-alert'; 6 | import { DealFormComponent } from './deal-form.component'; 7 | 8 | describe('DealFormComponent', () => { 9 | let component: DealFormComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [DealFormComponent], 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientTestingModule 18 | ], 19 | providers: [ 20 | AlertService, 21 | FormBuilder 22 | ] 23 | }) 24 | .compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(DealFormComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/features/user/account-info/account-info.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { FormBuilder } from '@angular/forms'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { AlertService } from 'carey-alert'; 6 | import { AccountInfoComponent } from './account-info.component'; 7 | 8 | describe('AccountInfoComponent', () => { 9 | let component: AccountInfoComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [AccountInfoComponent], 15 | imports: [ 16 | HttpClientTestingModule, 17 | RouterTestingModule 18 | ], 19 | providers: [ 20 | FormBuilder, 21 | AlertService 22 | ] 23 | }) 24 | .compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(AccountInfoComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/features/activities/add-activity/add-activity.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { HttpClientTestingModule } from "@angular/common/http/testing"; 4 | import { AlertService } from 'carey-alert'; 5 | import { FormBuilder } from '@angular/forms'; 6 | import { AddActivityComponent } from './add-activity.component'; 7 | 8 | describe('AddActivityComponent', () => { 9 | let component: AddActivityComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [AddActivityComponent], 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientTestingModule 18 | ], 19 | providers: [ 20 | AlertService, 21 | FormBuilder 22 | ] 23 | }) 24 | .compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(AddActivityComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact.component.css: -------------------------------------------------------------------------------- 1 | .vertical-layout { 2 | margin-bottom: 15px !important; 3 | } 4 | 5 | .empty-set-status-message { 6 | font-size: 14pt; 7 | font-weight: 400; 8 | } 9 | 10 | .address-type { 11 | font-size: 14pt; 12 | font-weight: 400; 13 | text-decoration: underline; 14 | margin-bottom: 5px; 15 | } 16 | 17 | .normal-detail { 18 | font-size: 12pt; 19 | font-weight: 500; 20 | } 21 | 22 | .phone-label { 23 | font-size: 12pt; 24 | font-weight: bold; 25 | } 26 | 27 | 28 | .has-authority { 29 | font-size: 12pt; 30 | color: limegreen; 31 | font-weight: 500; 32 | } 33 | 34 | .no-authority { 35 | font-size: 12pt; 36 | color: crimson; 37 | font-weight: 500; 38 | } 39 | 40 | .column-header { 41 | font-size: 12pt; 42 | font-weight: 500; 43 | text-decoration: underline; 44 | margin-bottom: 5px; 45 | } 46 | 47 | .column-layout { 48 | margin-bottom: 20px; 49 | margin-right: 80px; 50 | } 51 | 52 | .normal-status { 53 | font-size: 14pt; 54 | } 55 | 56 | .inactive-status { 57 | font-size: 14pt; 58 | font-weight: 500; 59 | color: crimson; 60 | } 61 | 62 | .free-text { 63 | font-size: 11pt; 64 | font-weight: 300; 65 | } 66 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phone-type-form/phone-type-form.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
6 |
7 | 10 | 11 |
12 |
13 | {{phoneCode}} 14 |
15 |
16 | 17 |
18 |
19 | Please enter a valid phone number 20 |
21 |
22 |
23 | -------------------------------------------------------------------------------- /src/app/features/deals/edit-deal/edit-deal.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { AlertService } from 'carey-alert'; 5 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 6 | 7 | import { EditDealComponent } from './edit-deal.component'; 8 | 9 | describe('EditDealComponent', () => { 10 | let component: EditDealComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async () => { 14 | await TestBed.configureTestingModule({ 15 | declarations: [EditDealComponent], 16 | imports: [ 17 | RouterTestingModule, 18 | HttpClientTestingModule 19 | ], 20 | providers: [ 21 | AlertService, 22 | BreadcrumbService 23 | ] 24 | }) 25 | .compileComponents(); 26 | }); 27 | 28 | beforeEach(() => { 29 | fixture = TestBed.createComponent(EditDealComponent); 30 | component = fixture.componentInstance; 31 | fixture.detectChanges(); 32 | }); 33 | 34 | it('should create', () => { 35 | expect(component).toBeTruthy(); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/app/features/contacts/edit-contact/edit-contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { HttpClientTestingModule } from "@angular/common/http/testing"; 4 | import { AlertService } from 'carey-alert'; 5 | import { EditContactComponent } from './edit-contact.component'; 6 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 7 | 8 | describe('EditContactComponent', () => { 9 | let component: EditContactComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | declarations: [EditContactComponent], 15 | imports: [ 16 | RouterTestingModule, 17 | HttpClientTestingModule 18 | ], 19 | providers: [ 20 | AlertService, 21 | BreadcrumbService 22 | ] 23 | }) 24 | .compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(EditContactComponent); 29 | component = fixture.componentInstance; 30 | fixture.detectChanges(); 31 | }); 32 | 33 | it('should create', () => { 34 | expect(component).toBeTruthy(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/app/features/ui/menu-list-item/menu-list-item.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { MatDialogModule } from '@angular/material/dialog'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { NavItem } from '../model/nav-item'; 6 | 7 | import { MenuListItemComponent } from './menu-list-item.component'; 8 | 9 | describe('MenuListItemComponent', () => { 10 | let component: MenuListItemComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async () => { 14 | await TestBed.configureTestingModule({ 15 | declarations: [MenuListItemComponent], 16 | imports: [ 17 | RouterTestingModule, 18 | HttpClientTestingModule, 19 | MatDialogModule 20 | ] 21 | }) 22 | .compileComponents(); 23 | }); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(MenuListItemComponent); 27 | component = fixture.componentInstance; 28 | 29 | let item = {} as NavItem; 30 | component.item = item; 31 | 32 | fixture.detectChanges(); 33 | }); 34 | 35 | it('should create', () => { 36 | expect(component).toBeTruthy(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/features/activities/recent-activities-by-contact/recent-activities-by-contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from "@angular/common/http/testing"; 3 | import { AlertService } from 'carey-alert'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | import { RecentActivitiesByContactComponent } from './recent-activities-by-contact.component'; 6 | 7 | describe('ActivitiesByContactComponent', () => { 8 | let component: RecentActivitiesByContactComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async () => { 12 | await TestBed.configureTestingModule({ 13 | declarations: [RecentActivitiesByContactComponent], 14 | imports: [ 15 | RouterTestingModule, 16 | HttpClientTestingModule 17 | ], 18 | providers: [ 19 | AlertService 20 | ] 21 | }) 22 | .compileComponents(); 23 | }); 24 | 25 | beforeEach(() => { 26 | fixture = TestBed.createComponent(RecentActivitiesByContactComponent); 27 | component = fixture.componentInstance; 28 | fixture.detectChanges(); 29 | }); 30 | 31 | it('should create', () => { 32 | expect(component).toBeTruthy(); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/features/user/profile-image/profile-image.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { User } from 'carey-user'; 5 | import { AlertService } from 'carey-alert'; 6 | 7 | import { ProfileImageComponent } from './profile-image.component'; 8 | 9 | describe('ProfileImageComponent', () => { 10 | let component: ProfileImageComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async () => { 14 | await TestBed.configureTestingModule({ 15 | declarations: [ProfileImageComponent], 16 | imports: [ 17 | HttpClientTestingModule, 18 | RouterTestingModule 19 | ], 20 | providers: [ 21 | AlertService 22 | ] 23 | }) 24 | .compileComponents(); 25 | }); 26 | 27 | beforeEach(() => { 28 | fixture = TestBed.createComponent(ProfileImageComponent); 29 | component = fixture.componentInstance; 30 | 31 | let user = {} as User; 32 | component.user = user; 33 | 34 | fixture.detectChanges(); 35 | }); 36 | 37 | it('should create', () => { 38 | expect(component).toBeTruthy(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /src/app/services/auth-guard.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | import { AlertService } from 'carey-alert'; 4 | import { AuthenticationService } from 'carey-auth'; 5 | 6 | @Injectable({ providedIn: 'root' }) 7 | export class AuthGuard implements CanActivate { 8 | constructor(private router: Router, private authenticationService: AuthenticationService, 9 | private alertService: AlertService) { } 10 | 11 | canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { 12 | let routeMessage: string = null; 13 | const isTokenExpired = this.authenticationService.isTokenExpired(); 14 | 15 | if (!isTokenExpired) { 16 | const isLoggedIn = this.authenticationService.isLoggedIn(); 17 | 18 | if (isLoggedIn) { 19 | return true; 20 | } else { 21 | routeMessage = "You must login to continue."; 22 | } 23 | } else { 24 | routeMessage = "Your session has expired." 25 | } 26 | 27 | if (routeMessage) this.alertService.info(routeMessage, { keepAfterRouteChange: false }); 28 | 29 | this.router.navigate(['/login'], { queryParams: { returnUrl: state.url } }); 30 | return false; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/app/features/accounts/view-account/view-account.component.css: -------------------------------------------------------------------------------- 1 | .vertical-layout { 2 | margin-bottom: 15px !important; 3 | } 4 | 5 | .empty-set-status-message { 6 | font-size: 14pt; 7 | font-weight: 400; 8 | } 9 | 10 | .address-type { 11 | font-size: 14pt; 12 | font-weight: 400; 13 | text-decoration: underline; 14 | margin-bottom: 5px; 15 | } 16 | 17 | .normal-detail { 18 | font-size: 12pt; 19 | font-weight: 500; 20 | } 21 | 22 | .phone-label { 23 | font-size: 12pt; 24 | font-weight: bold; 25 | } 26 | 27 | 28 | .has-authority { 29 | font-size: 12pt; 30 | color: limegreen; 31 | font-weight: 500; 32 | } 33 | 34 | .no-authority { 35 | font-size: 12pt; 36 | color: crimson; 37 | font-weight: 500; 38 | } 39 | 40 | .column-header { 41 | font-size: 12pt; 42 | font-weight: 500; 43 | text-decoration: underline; 44 | margin-bottom: 5px; 45 | } 46 | 47 | .column-layout { 48 | margin-bottom: 20px; 49 | margin-right: 80px; 50 | } 51 | 52 | .normal-status { 53 | font-size: 14pt; 54 | color: green; 55 | } 56 | 57 | yellow-status { 58 | font-size: 14pt; 59 | color: goldenrod; 60 | } 61 | 62 | .inactive-status { 63 | font-size: 14pt; 64 | font-weight: 500; 65 | color: crimson; 66 | } 67 | 68 | .free-text { 69 | font-size: 11pt; 70 | font-weight: 300; 71 | } 72 | -------------------------------------------------------------------------------- /src/app/features/activities/edit-activity/edit-activity.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from "@angular/common/http/testing"; 3 | import { AlertService } from 'carey-alert'; 4 | import { FormBuilder } from '@angular/forms'; 5 | import { EditActivityComponent } from './edit-activity.component'; 6 | import { RouterTestingModule } from '@angular/router/testing'; 7 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 8 | 9 | describe('EditActivityComponent', () => { 10 | let component: EditActivityComponent; 11 | let fixture: ComponentFixture; 12 | 13 | beforeEach(async () => { 14 | await TestBed.configureTestingModule({ 15 | declarations: [EditActivityComponent], 16 | imports: [ 17 | RouterTestingModule, 18 | HttpClientTestingModule 19 | ], 20 | providers: [ 21 | AlertService, 22 | FormBuilder, 23 | BreadcrumbService 24 | ] 25 | }) 26 | .compileComponents(); 27 | }); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(EditActivityComponent); 31 | component = fixture.componentInstance; 32 | fixture.detectChanges(); 33 | }); 34 | 35 | it('should create', () => { 36 | expect(component).toBeTruthy(); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/app/login/login.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 |

CRM App

10 |
11 | 12 |
13 | 14 |
15 | 16 | 19 |
20 | 21 |
22 |
23 |
24 |

Welcome to the CRM App

25 |
26 | 27 |
28 |

Please sign in below

29 |
30 |
31 | 32 | 33 | 34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /src/app/features/activities/directives/display-activity-date-difference.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnInit } from '@angular/core'; 2 | import { SingleClassBaseDirective } from '../../../directives/single-class-base.directive'; 3 | import { DateService } from '../../../services/date.service'; 4 | import { Activity } from '../models/activity'; 5 | import { ActivityService } from '../service/activity.service'; 6 | 7 | @Directive({ 8 | selector: '[displayActivityDateDifference]' 9 | }) 10 | export class DisplayActivityDateDifferenceDirective extends SingleClassBaseDirective implements OnInit { 11 | 12 | @Input('displayActivityDateDifference') currentDateDifference: number; 13 | @Input('activity') activity: Activity; 14 | 15 | constructor() { 16 | super(); 17 | } 18 | 19 | ngOnInit() { 20 | if (this.currentDateDifference) { 21 | this.setClass(); 22 | } 23 | } 24 | 25 | private setClass() { 26 | let className: string = ''; 27 | 28 | if (this.currentDateDifference > 0 && this.currentDateDifference < 36000) className = 'info-text'; 29 | 30 | if (this.activity.type) { 31 | if (this.currentDateDifference < 0 && this.activity.type.usesStatus) className = 'error-text'; 32 | if (this.currentDateDifference < 0 && !this.activity.type.usesStatus) className = 'warning-text'; 33 | } 34 | 35 | this._elementClass = className; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/review-form/review-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { addressTypes } from '../../../../models/address-type'; 3 | import { DisplayValueMap } from '../../../../models/name-value-map'; 4 | import { phoneTypes } from '../../../../models/phone-type'; 5 | import { sources } from '../../../../models/source'; 6 | import { DisplayValueMapService } from '../../../ui/service/display-map.service'; 7 | import { contactStatuses } from '../../constants/contact-status'; 8 | import { linesOfBusiness } from '../../constants/line-of-business'; 9 | import { Contact } from '../../models/contact'; 10 | 11 | @Component({ 12 | selector: 'contact-review-form', 13 | templateUrl: './review-form.component.html', 14 | styleUrls: ['./review-form.component.css'] 15 | }) 16 | export class ReviewFormComponent implements OnInit { 17 | 18 | @Input() contact: Contact; 19 | @Input() errorMessages: string[] = []; 20 | 21 | availableAddressTypes: DisplayValueMap[] = addressTypes; 22 | availablePhoneTypes: DisplayValueMap[] = phoneTypes; 23 | availableContactStatuses: DisplayValueMap[] = contactStatuses; 24 | availableLinesOfBusiness: DisplayValueMap[] = linesOfBusiness; 25 | availableSources: DisplayValueMap[] = sources; 26 | 27 | constructor(private displayValueMapService: DisplayValueMapService) { } 28 | 29 | ngOnInit(): void { 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/app/features/accounts/services/account.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { environment } from '../../../../environments/environment'; 5 | import { Account } from '../../accounts/models/account'; 6 | 7 | 8 | const baseUrl: string = environment.baseCustomerServiceUrl; 9 | 10 | @Injectable({ providedIn: 'root' }) 11 | export class AccountService { 12 | 13 | constructor(private http: HttpClient) { } 14 | 15 | fetchAllAccounts(): Observable { 16 | let url = `${baseUrl}/accounts`; 17 | console.log("Fetch all accounts URL is " + url); 18 | 19 | return this.http.get(url); 20 | } 21 | 22 | fetchById(id: string): Observable { 23 | let url = `${baseUrl}/accounts/${id}`; 24 | console.log("Fetch account URL is " + url); 25 | 26 | return this.http.get(url); 27 | } 28 | 29 | createAccount(account: Account): Observable { 30 | let url = `${baseUrl}/accounts`; 31 | console.log("Create account URL is " + url); 32 | 33 | return this.http.post(url, account); 34 | } 35 | 36 | updateAccount(account: Account): Observable { 37 | let url = `${baseUrl}/accounts/${account.id}`; 38 | console.log("Update account URL is " + url); 39 | 40 | return this.http.put(url, account); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/features/contacts/view-contact/view-contact.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { RouterTestingModule } from '@angular/router/testing'; 4 | import { AlertService } from 'carey-alert'; 5 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 6 | import { Contact } from '../models/contact'; 7 | 8 | import { ViewContactComponent } from './view-contact.component'; 9 | 10 | describe('ViewContactComponent', () => { 11 | let component: ViewContactComponent; 12 | let fixture: ComponentFixture; 13 | 14 | beforeEach(async () => { 15 | await TestBed.configureTestingModule({ 16 | declarations: [ViewContactComponent], 17 | imports: [ 18 | RouterTestingModule, 19 | HttpClientTestingModule 20 | ], 21 | providers: [ 22 | AlertService, 23 | BreadcrumbService 24 | ] 25 | }) 26 | .compileComponents(); 27 | }); 28 | 29 | beforeEach(() => { 30 | fixture = TestBed.createComponent(ViewContactComponent); 31 | component = fixture.componentInstance; 32 | 33 | //TODO: fix this 34 | let contact: Contact = {} as Contact; 35 | //component.contact = contact; 36 | 37 | fixture.detectChanges(); 38 | }); 39 | 40 | it('should create', () => { 41 | expect(component).toBeTruthy(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/app/features/accounts/constants/industry.ts: -------------------------------------------------------------------------------- 1 | import { DisplayValueMap } from '../../../models/name-value-map'; 2 | 3 | export const industries: DisplayValueMap[] = [ 4 | { display: 'Administrative Support', value: 'ADMINISTRATIVE_SUPPORT' }, 5 | { display: 'Agriculture', value: 'AGRICULTURE' }, 6 | { display: 'Amusement', value: 'AMUSEMENT' }, 7 | { display: 'Apparel', value: 'APPAREL' }, 8 | { display: 'Arts', value: 'ARTS' }, 9 | { display: 'Beverage', value: 'BEVERAGE' }, 10 | { display: 'Broadcasting', value: 'BROADCASTING' }, 11 | { display: 'Chemical Manufacturing', value: 'CHEMICAL_MANUFACTURING' }, 12 | { display: 'Construction', value: 'CONSTRUCTION' }, 13 | { display: 'Data Processing', value: 'DATA_PROCESSING' }, 14 | { display: 'Education', value: 'EDUCATION' }, 15 | { display: 'Electronics', value: 'ELECTRONICS' }, 16 | { display: 'Energy', value: 'ENERGY' }, 17 | { display: 'Finance', value: 'FINANCE' }, 18 | { display: 'Food Service', value: 'FOOD_SERVICE' }, 19 | { display: 'Furniture', value: 'FURNITURE' }, 20 | { display: 'Healthcare', value: 'HEALTHCARE' }, 21 | { display: 'Hospitality', value: 'HOSPITALITY' }, 22 | { display: 'Insurance', value: 'INSURANCE' }, 23 | { display: 'Material', value: 'MATERIALS' }, 24 | { display: 'Mining', value: 'MINING' }, 25 | { display: 'Public Sector', value: 'PUBLIC_SECTOR' }, 26 | { display: 'Real Estate', value: 'REAL_ESTATE' }, 27 | { display: 'Retail', value: 'RETAIL' }, 28 | { display: 'Transportation', value: 'TRANSPORTATION' }, 29 | { display: 'Other', value: 'OTHER' } 30 | ]; 31 | -------------------------------------------------------------------------------- /src/app/features/activities/pipes/activity-date-display.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DateService } from '../../../services/date.service'; 3 | import { Activity } from '../models/activity'; 4 | 5 | @Pipe({ 6 | name: 'activityDateDisplay' 7 | }) 8 | export class ActivityDateDisplayPipe implements PipeTransform { 9 | 10 | constructor(private dateService: DateService) { } 11 | 12 | transform(activity: Activity): string { 13 | let display: string = ''; 14 | 15 | if (activity) { 16 | if (activity.startDate) { 17 | let startDate: number = this.dateService.convertToLocal(activity.startDate); 18 | 19 | display = this.dateService.getShortDateAndTimeDisplay(startDate); 20 | 21 | if (activity.endDate) { 22 | let endDate = this.dateService.convertToLocal(activity.endDate); 23 | 24 | let startTimeDisplay: string = this.dateService.getShortTimeDisplay(startDate); 25 | let endTimeDisplay: string = this.dateService.getShortTimeDisplay(endDate); 26 | 27 | let startDateDisplay: string = this.dateService.getShortDateDisplay(startDate); 28 | let endDateDisplay: string = this.dateService.getShortDateDisplay(endDate); 29 | 30 | if (startDateDisplay == endDateDisplay) { 31 | display = startDateDisplay + " " + startTimeDisplay + " - " + endTimeDisplay; 32 | } else { 33 | display = display + " - " + this.dateService.getShortDateAndTimeDisplay(endDate); 34 | } 35 | } 36 | } 37 | } 38 | 39 | return display; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phones-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChildren, Input } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { Phone } from '../../../../models/phone'; 4 | import { phoneTypes } from '../../../../models/phone-type'; 5 | import { Contact } from '../../models/contact'; 6 | import { PhoneTypeFormComponent } from './phone-type-form/phone-type-form.component'; 7 | 8 | 9 | @Component({ 10 | selector: 'contact-phones-form', 11 | templateUrl: './phones-form.component.html', 12 | styleUrls: ['./phones-form.component.css'] 13 | }) 14 | export class PhonesFormComponent implements OnInit { 15 | 16 | @ViewChildren(PhoneTypeFormComponent) phoneTypeComponents: PhoneTypeFormComponent[]; 17 | 18 | availablePhoneTypes = phoneTypes; 19 | 20 | @Input() contact: Contact; 21 | 22 | constructor(private fb: FormBuilder) { } 23 | 24 | ngOnInit() { 25 | } 26 | 27 | populateContact(contact: Contact) { 28 | let phones: Phone[] = []; 29 | 30 | this.phoneTypeComponents.forEach((element, index) => { 31 | let phone = {} as Phone; 32 | let phoneForm: FormGroup = element.phoneTypeFormGroup; 33 | let countryCode: string = element.selectedCountryCode; 34 | 35 | phone.phoneType = phoneForm.controls['phoneType'].value; 36 | phone.phone = phoneForm.controls['phone'].value; 37 | phone.countryCode = countryCode; 38 | 39 | if (phone.phone && phone.phone.trim().length > 0) { 40 | phones.push(phone); 41 | } 42 | }); 43 | 44 | contact.phones = phones; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/contact-form.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { AlertService } from 'carey-alert'; 3 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 4 | import { ContactFormComponent } from './contact-form.component'; 5 | import { AddressesFormComponent } from './addresses-form/addresses-form.component'; 6 | import { BasicInfoFormComponent } from './basic-info-form/basic-info-form.component'; 7 | import { PhonesFormComponent } from './phones-form/phones-form.component'; 8 | import { FormBuilder } from '@angular/forms'; 9 | import { MatAutocompleteModule } from '@angular/material/autocomplete'; 10 | 11 | describe('ContactFormComponent', () => { 12 | let component: ContactFormComponent; 13 | let fixture: ComponentFixture; 14 | 15 | beforeEach(async () => { 16 | await TestBed.configureTestingModule({ 17 | declarations: [ 18 | ContactFormComponent, 19 | AddressesFormComponent, 20 | BasicInfoFormComponent, 21 | PhonesFormComponent 22 | ], 23 | imports: [ 24 | HttpClientTestingModule, 25 | MatAutocompleteModule 26 | ], 27 | providers: [ 28 | AlertService, 29 | FormBuilder 30 | ] 31 | }) 32 | .compileComponents(); 33 | }); 34 | 35 | beforeEach(() => { 36 | fixture = TestBed.createComponent(ContactFormComponent); 37 | component = fixture.componentInstance; 38 | fixture.detectChanges(); 39 | }); 40 | 41 | it('should create', () => { 42 | expect(component).toBeTruthy(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/app/features/activities/directives/display-outcome-status.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnInit } from '@angular/core'; 2 | import { MultipleClassesBaseDirective } from '../../../directives/multiple-classes-base.directive'; 3 | import { Activity } from '../models/activity'; 4 | import { ActivityOutcome } from '../models/activity-outcome'; 5 | import { ActivityService } from '../service/activity.service'; 6 | 7 | @Directive({ 8 | selector: '[displayOutcomeStatus]' 9 | }) 10 | export class DisplayOutcomeStatusDirective extends MultipleClassesBaseDirective implements OnInit { 11 | 12 | @Input('displayOutcomeStatus') activity: Activity; 13 | 14 | constructor(private activityService: ActivityService) { 15 | super(); 16 | } 17 | 18 | ngOnInit() { 19 | this._elementClass.push('badge'); 20 | this.addExtraClass(); 21 | } 22 | 23 | private addExtraClass() { 24 | if (this.activity) { 25 | let extraClass: string = null; 26 | let outcome: ActivityOutcome = this.activity.outcome; 27 | let status: string = this.activity.status; 28 | 29 | if (outcome && outcome.sentiment) { 30 | let sentiment = outcome.sentiment; 31 | 32 | if (sentiment == 'POSITIVE') extraClass = 'badge-success'; 33 | else if (sentiment == 'NEGATIVE') extraClass = 'badge-error'; 34 | else extraClass = 'badge-info'; 35 | } else { 36 | if (status == 'COMPLETED') extraClass = 'badge-success'; 37 | else if (this.activityService.isOverdue(this.activity)) extraClass = 'badge-error'; 38 | else if (status == 'ON_HOLD') extraClass = 'badge-warning'; 39 | } 40 | 41 | if (extraClass) this._elementClass.push(extraClass); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/phones-form/phone-type-form/phone-type-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; 3 | import { GeoService } from 'carey-geo'; 4 | import { Phone } from '../../../../../models/phone'; 5 | import { Contact } from '../../../models/contact'; 6 | 7 | @Component({ 8 | selector: 'contact-phone-type-form', 9 | templateUrl: './phone-type-form.component.html', 10 | styleUrls: ['./phone-type-form.component.css'] 11 | }) 12 | export class PhoneTypeFormComponent implements OnInit { 13 | 14 | phoneTypeFormGroup: FormGroup; 15 | 16 | @Input() phoneType: string; 17 | @Input() contact: Contact; 18 | 19 | selectedCountryCode = 'us'; 20 | phoneCode = '1'; 21 | countryCodes = ['us', 'ca', 'de', 'mx', 'br', 'pt', 'cn', 'be', 'jp', 'ph', 'lu', 'bs']; 22 | 23 | constructor(private fb: FormBuilder, private geoService: GeoService) { } 24 | 25 | ngOnInit() { 26 | this.initForm(); 27 | } 28 | 29 | private initForm() { 30 | if (!this.contact) this.contact = {} as Contact; 31 | 32 | let phone: Phone = (this.contact.phones) ? this.contact.phones.find(ph => ph.phoneType === this.phoneType) : null; 33 | let phoneNumber = (phone) ? phone.phone : ''; 34 | 35 | this.phoneTypeFormGroup = this.fb.group({ 36 | 'phoneType': [this.phoneType], 37 | 'phone': [phoneNumber, [Validators.pattern('[A-Za-z0-9\-\_ ()]+')]] 38 | }); 39 | } 40 | 41 | changeSelectedCountryCode(value: string): void { 42 | this.selectedCountryCode = value; 43 | this.phoneCode = this.geoService.findCountryCodeByTwoLetterAbbreviation(this.selectedCountryCode); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/review-form/review-form.component.css: -------------------------------------------------------------------------------- 1 | .review-header { 2 | font-size: 14pt; 3 | font-weight: 600; 4 | text-decoration: underline; 5 | } 6 | 7 | .name { 8 | font-size: 12pt; 9 | font-weight: bold; 10 | } 11 | 12 | .title { 13 | font-size: 12pt; 14 | margin-top: 5px; 15 | } 16 | 17 | .company { 18 | font-size: 12pt; 19 | margin-top: 5px; 20 | } 21 | 22 | .email { 23 | font-size: 12pt; 24 | margin-top: 5px; 25 | } 26 | 27 | .address-section { 28 | margin-top: 30px; 29 | } 30 | 31 | .address-type { 32 | text-decoration: underline; 33 | font-weight:500; 34 | margin-bottom: 5px; 35 | font-size: 12pt; 36 | } 37 | 38 | .address-line { 39 | margin-top: 5px; 40 | font-size: 12pt; 41 | } 42 | 43 | .phone-section { 44 | margin-top: 30px; 45 | } 46 | 47 | .phone { 48 | font-size: 12pt; 49 | margin-bottom: 5px; 50 | } 51 | 52 | .phone-header { 53 | text-decoration: underline; 54 | font-weight: 500; 55 | margin-bottom: 5px; 56 | font-size: 12pt; 57 | } 58 | 59 | .other-details-section { 60 | margin-top: 30px 61 | } 62 | 63 | .other-details-header { 64 | text-decoration: underline; 65 | font-weight: 500; 66 | margin-bottom: 5px; 67 | font-size: 12pt; 68 | } 69 | 70 | .other-label { 71 | font-size: 12pt; 72 | font-weight: 500; 73 | } 74 | 75 | .other-line { 76 | margin-bottom: 10px; 77 | } 78 | 79 | .other-value { 80 | font-size: 12pt; 81 | margin-left: 10px; 82 | } 83 | 84 | .lob-section { 85 | margin-top: 30px 86 | } 87 | 88 | .lob-header { 89 | text-decoration: underline; 90 | font-weight: 500; 91 | margin-bottom: 5px; 92 | font-size: 12pt; 93 | } 94 | 95 | .lob-line { 96 | margin-bottom: 5px; 97 | } 98 | -------------------------------------------------------------------------------- /src/app/features/service/file-upload.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpEvent, HttpRequest, HttpErrorResponse, HttpHandler } from '@angular/common/http'; 3 | import { Observable, throwError } from 'rxjs'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | @Injectable({providedIn: 'root'}) 7 | export class UploadFileService { 8 | 9 | constructor(private handler: HttpHandler) { } 10 | 11 | pushFileToStorage(file: File, url: string): Observable> { 12 | console.log("Pushing file to " + url); 13 | const data: FormData = new FormData(); 14 | data.append('file', file); 15 | 16 | const newRequest = new HttpRequest('POST', url, data); 17 | 18 | return this.handler.handle(newRequest).pipe(catchError(this.handleError)); 19 | } 20 | 21 | private handleError(error: HttpErrorResponse) { 22 | if (error.error instanceof ErrorEvent) { 23 | // A client-side or network error occurred. Handle it accordingly. 24 | console.error('An error occurred:', error.error.message); 25 | } else { 26 | // The backend returned an unsuccessful response code. 27 | console.error( 28 | `Backend returned code ${error.status}, ` + 29 | `body was: ${error.error}`); 30 | 31 | if (error.status == 403) { 32 | throw new Error("You are not permitted to make changes on this account"); 33 | } 34 | 35 | throw new Error("Unexpected error - please try again later"); 36 | } 37 | 38 | // return an observable with a user-facing error message 39 | return throwError("Unexpected error - please try again later"); 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/app/features/contacts/services/contact-validation.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Contact } from '../models/contact'; 3 | import { FormGroup } from '@angular/forms'; 4 | import { BasicInfoFormComponent } from '../contact-form/basic-info-form/basic-info-form.component'; 5 | import { ValidationService } from 'carey-validation'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class ContactValidationService { 9 | 10 | constructor(private validationService: ValidationService) {} 11 | 12 | public validateForms(basicInfoComponent: BasicInfoFormComponent, contact: Contact): string[] { 13 | let errorMessages: string[] = []; 14 | 15 | errorMessages = errorMessages.concat(this.validateBasicInfoForm(basicInfoComponent)); 16 | 17 | this.handleAdditionalValidations(errorMessages, contact); 18 | 19 | return errorMessages; 20 | } 21 | 22 | private handleAdditionalValidations(errorMessages: string[], contact: Contact) { 23 | this.validateAtLeastOneContactMethod(contact, errorMessages); 24 | } 25 | 26 | private validateBasicInfoForm(basicInfoComponent: BasicInfoFormComponent): string[] { 27 | let basicInfoForm: FormGroup = basicInfoComponent.basicInfoFormGroup; 28 | 29 | let errorMessages: string[] = this.validationService.validateForm(basicInfoForm); 30 | 31 | return errorMessages; 32 | } 33 | 34 | private validateAtLeastOneContactMethod(contact: Contact, errorMessages: string[]) { 35 | if (!contact.email 36 | && (!contact.addresses || contact.addresses.length == 0) 37 | && (!contact.phones || contact.phones.length == 0)) { 38 | 39 | errorMessages.push("Please include at least one method of contact (phone, email, address)"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/app/features/deals/deals-by-contact/deals-by-contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ViewEncapsulation } from '@angular/core'; 2 | import { Deal } from '../models/deal'; 3 | import { DealService } from '../service/deal.service'; 4 | import { DateService } from '../../../services/date.service'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-deals-by-contact', 9 | encapsulation: ViewEncapsulation.None, 10 | templateUrl: './deals-by-contact.component.html', 11 | styleUrls: ['./deals-by-contact.component.css'] 12 | }) 13 | export class DealsByContactComponent implements OnInit { 14 | 15 | @Input() contactId: string; 16 | deals: Deal[]; 17 | loading: boolean = true; 18 | errorLoading: boolean = false; 19 | 20 | constructor(private dealService: DealService, private dateService: DateService, 21 | private router: Router) { } 22 | 23 | ngOnInit(): void { 24 | this.loadDeals(); 25 | } 26 | 27 | private loadDeals() { 28 | this.dealService.fetchDealsByContactId(this.contactId).subscribe( 29 | (deals: Deal[]) => this.handleFetchDealsResponse(deals), 30 | err => this.handleFetchDealsError(err) 31 | ); 32 | } 33 | 34 | private handleFetchDealsResponse(deals: Deal[]) { 35 | this.deals = deals; 36 | this.loading = false; 37 | } 38 | 39 | private handleFetchDealsError(err: Error) { 40 | this.errorLoading = true; 41 | this.loading = false; 42 | 43 | console.error(err); 44 | } 45 | 46 | editDeal(dealId: string) { 47 | let route = '/deals/edit-deal'; 48 | this.router.navigate([route], { queryParams: { dealId: dealId } }); 49 | } 50 | 51 | viewDeal(dealId: string) { 52 | let route = '/deals/view-deal'; 53 | this.router.navigate([route], { queryParams: { dealId: dealId } }); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | import { FeaturesComponent } from './features/features.component'; 4 | import { AuthGuard } from './services/auth-guard.service'; 5 | 6 | const routes: Routes = [ 7 | { path: '', pathMatch: 'full', redirectTo: 'home' }, 8 | { path: 'home', loadChildren: () => import('./home/home.module').then(m => m.HomeModule) }, 9 | { path: 'login', loadChildren: () => import('./login/login.module').then(m => m.LoginModule) }, 10 | { 11 | path: '', 12 | component: FeaturesComponent, 13 | children: [ 14 | { path: 'dashboard', canActivate: [AuthGuard], loadChildren: () => import('./features/dashboard/dashboard.module').then(m => m.DashboardModule) }, 15 | { path: 'user', canActivate: [AuthGuard], loadChildren: () => import('./features/user/user.module').then(m => m.UserModule) }, 16 | { path: 'contacts', canActivate: [AuthGuard], loadChildren: () => import('./features/contacts/contacts.module').then(m => m.ContactsModule) }, 17 | { path: 'accounts', canActivate: [AuthGuard], loadChildren: () => import('./features/accounts/accounts.module').then(m => m.AccountsModule) }, 18 | { path: 'activities', canActivate: [AuthGuard], loadChildren: () => import('./features/activities/activities.module').then(m => m.ActivitiesModule) }, 19 | { path: 'deals', canActivate: [AuthGuard], loadChildren: () => import('./features/deals/deals.module').then(m => m.DealsModule) } 20 | ] 21 | } 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [ 26 | RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' }) 27 | ], 28 | exports: [RouterModule], 29 | providers: [] 30 | }) 31 | export class AppRoutingModule { } 32 | -------------------------------------------------------------------------------- /src/app/features/user/email/message/message.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { EmailService } from '../service/email.service'; 3 | import { switchMap } from 'rxjs/operators'; 4 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 5 | import { Email } from '../models/email'; 6 | import { AlertService } from 'carey-alert'; 7 | 8 | 9 | @Component({ 10 | selector: 'app-message', 11 | templateUrl: './message.component.html', 12 | styleUrls: ['./message.component.css'] 13 | }) 14 | export class MessageComponent implements OnInit { 15 | 16 | email: Email; 17 | loading: boolean = true; 18 | 19 | constructor(private emailService: EmailService, private route: ActivatedRoute, 20 | private alertService: AlertService, private router: Router) { } 21 | 22 | ngOnInit(): void { 23 | this.fetchEmail(); 24 | } 25 | 26 | private fetchEmail() { 27 | let email$ = this.route.queryParamMap.pipe( 28 | switchMap((params: ParamMap) => 29 | this.emailService.fetchMessageById(params.get('id'))) 30 | ); 31 | 32 | email$.subscribe( 33 | (email: Email) => this.handleEmailResponse(email), 34 | err => this.handleError(err) 35 | ); 36 | } 37 | 38 | private handleEmailResponse(email: Email) { 39 | //console.log(email); 40 | this.email = email; 41 | this.loading = false; 42 | } 43 | 44 | private handleError(err: Error) { 45 | console.error(err); 46 | this.alertService.error("Problem - please contact support!"); 47 | this.loading = false; 48 | } 49 | 50 | back() { 51 | let route = '/user/email/inbox'; 52 | this.router.navigate([route]); 53 | } 54 | 55 | reply() { 56 | let route = '/user/email/compose-email'; 57 | this.router.navigate([route], { state: { currentEmail: this.email } }); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/app/features/activities/recent-activities-by-contact/recent-activities-by-contact.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input, ViewEncapsulation } from '@angular/core'; 2 | import { ActivityService } from '../service/activity.service'; 3 | import { Activity } from '../models/activity'; 4 | import { DateService } from '../../../services/date.service'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-recent-activities-by-contact', 9 | encapsulation: ViewEncapsulation.None, 10 | templateUrl: './recent-activities-by-contact.component.html', 11 | styleUrls: ['./recent-activities-by-contact.component.css'] 12 | }) 13 | export class RecentActivitiesByContactComponent implements OnInit { 14 | 15 | @Input() contactId: string; 16 | activities: Activity[]; 17 | loading: boolean = true; 18 | errorLoading: boolean = false; 19 | 20 | constructor(private activityService: ActivityService, private router: Router, 21 | private dateService: DateService) { } 22 | 23 | ngOnInit(): void { 24 | this.loadActivities(); 25 | } 26 | 27 | private loadActivities() { 28 | this.activityService.fetchRecentActivitiesByContactId(this.contactId).subscribe( 29 | (activities: Activity[]) => this.handleFetchActivitiesResponse(activities), 30 | err => this.handleFetchActivitiesError(err) 31 | ); 32 | } 33 | 34 | private handleFetchActivitiesResponse(activities: Activity[]) { 35 | this.activities = activities; 36 | this.loading = false; 37 | } 38 | 39 | private handleFetchActivitiesError(err: Error) { 40 | console.error(err); 41 | this.loading = false; 42 | this.errorLoading = true; 43 | } 44 | 45 | editActivity(activityId: string) { 46 | let route = '/activities/edit-activity'; 47 | this.router.navigate([route], { queryParams: { activityId: activityId } }); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/app/features/user/email/email-redirect/email-redirect.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 3 | import { User, UserService } from 'carey-user'; 4 | import { switchMap } from 'rxjs/operators'; 5 | import { EmailService } from '../service/email.service'; 6 | 7 | @Component({ 8 | selector: 'app-email-redirect', 9 | templateUrl: './email-redirect.component.html', 10 | styleUrls: ['./email-redirect.component.css'] 11 | }) 12 | export class EmailRedirectComponent implements OnInit { 13 | 14 | constructor(private emailService: EmailService, private route: ActivatedRoute, 15 | private router: Router, private userService: UserService) { } 16 | 17 | ngOnInit(): void { 18 | let redirect$ = this.route.queryParamMap.pipe( 19 | switchMap((params: ParamMap) => 20 | this.emailService.fetchToken(params.get('code'))) 21 | ); 22 | 23 | redirect$.subscribe( 24 | (token: string) => this.handleResponse(token), 25 | err => this.handleError(err) 26 | ); 27 | } 28 | 29 | private handleResponse(token: string) { 30 | //console.log("token is " + token); 31 | let user: User = this.userService.user; 32 | this.emailService.updateUserEmailChoice(user, 'GMAIL').subscribe( 33 | (response: any) => this.handleUpdateEmailChoiceResponse(response), 34 | err => this.handleUpdateEmailChoiceError(err) 35 | ) 36 | } 37 | 38 | private handleUpdateEmailChoiceResponse(response: any) { 39 | this.reroute(); 40 | } 41 | 42 | private handleUpdateEmailChoiceError(err: Error) { 43 | console.error(err); 44 | } 45 | 46 | private reroute() { 47 | let route = '/user/email/inbox'; 48 | this.router.navigate([route]); 49 | } 50 | 51 | private handleError(err: Error) { 52 | console.error(err); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/app/features/activities/activities-list/activities-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | import { DateService } from '../../../services/date.service'; 4 | import { Activity } from '../models/activity'; 5 | import { ActivitySearchCriteria } from '../models/activity-search-criteria'; 6 | import { ActivityService } from '../service/activity.service'; 7 | 8 | @Component({ 9 | selector: 'app-activities-list', 10 | templateUrl: './activities-list.component.html', 11 | styleUrls: ['./activities-list.component.css'] 12 | }) 13 | export class ActivitiesListComponent implements OnInit { 14 | 15 | @Input() activitySearchCriteria: ActivitySearchCriteria; 16 | @Input() title: string; 17 | 18 | activities: Activity[] 19 | loading: boolean = true; 20 | errorLoading: boolean = false; 21 | 22 | 23 | constructor(private activityService: ActivityService, private router: Router, 24 | private dateService: DateService) { } 25 | 26 | ngOnInit(): void { 27 | this.loadActivities(); 28 | } 29 | 30 | private loadActivities() { 31 | this.activityService.fetchActivitiesByCriteria(this.activitySearchCriteria).subscribe( 32 | (activities: Activity[]) => this.handleFetchActivitiesResponse(activities), 33 | err => this.handleFetchActivitiesError(err) 34 | ); 35 | } 36 | 37 | private handleFetchActivitiesResponse(activities: Activity[]) { 38 | this.activities = activities; 39 | this.loading = false; 40 | } 41 | 42 | private handleFetchActivitiesError(err: Error) { 43 | console.error(err); 44 | this.loading = false; 45 | this.errorLoading = true; 46 | } 47 | 48 | editActivity(activityId: string) { 49 | let route = '/activities/edit-activity'; 50 | this.router.navigate([route], { queryParams: { activityId: activityId } }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/features/deals/edit-deal/edit-deal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { switchMap } from 'rxjs/operators'; 3 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 4 | import { AlertService } from 'carey-alert'; 5 | import { HttpErrorResponse } from '@angular/common/http'; 6 | import { Deal } from '../models/deal'; 7 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 8 | import { DealService } from '../service/deal.service'; 9 | 10 | @Component({ 11 | selector: 'app-edit-deal', 12 | templateUrl: './edit-deal.component.html', 13 | styleUrls: ['./edit-deal.component.css'] 14 | }) 15 | export class EditDealComponent implements OnInit { 16 | 17 | loading: boolean = true; 18 | deal: Deal; 19 | 20 | constructor(private route: ActivatedRoute, 21 | private alertService: AlertService, private router: Router, 22 | private dealService: DealService, private breadcrumbService: BreadcrumbService) { } 23 | 24 | ngOnInit(): void { 25 | this.loadDeal(); 26 | } 27 | 28 | private loadDeal() { 29 | let deal$ = this.route.queryParamMap.pipe( 30 | switchMap((params: ParamMap) => 31 | this.dealService.fetchDealById(params.get('dealId'))) 32 | ); 33 | 34 | deal$.subscribe( 35 | (deal: Deal) => this.handleDealResponse(deal), 36 | err => this.handleDealError(err) 37 | ); 38 | } 39 | 40 | private handleDealResponse(deal: Deal) { 41 | this.deal = deal; 42 | this.loading = false; 43 | this.breadcrumbService.updateBreadcrumb("Edit " + this.deal.name); 44 | } 45 | 46 | private handleDealError(err: Error) { 47 | this.loading = false; 48 | console.error(err); 49 | 50 | let alertMessage: string = 'Problem loading deal'; 51 | 52 | if (err instanceof HttpErrorResponse) { 53 | if (err.status) { 54 | if (err.status == 404) { 55 | alertMessage = 'Deal with that ID does not exist'; 56 | } 57 | } 58 | } 59 | 60 | this.alertService.error(alertMessage); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/features/contacts/status-progress-bar/status-progress-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { Contact } from '../models/contact'; 3 | import { contactStatuses } from '../constants/contact-status'; 4 | import { AlertService } from 'carey-alert'; 5 | import { DisplayValueMap } from '../../../models/name-value-map'; 6 | import { ContactService } from '../services/contact.service'; 7 | 8 | @Component({ 9 | selector: 'app-status-progress-bar', 10 | templateUrl: './status-progress-bar.component.html', 11 | styleUrls: ['./status-progress-bar.component.css'] 12 | }) 13 | export class StatusProgressBarComponent implements OnInit { 14 | 15 | @Input() contact: Contact; 16 | 17 | availableContactStatuses: DisplayValueMap[] = contactStatuses.filter(status => status.value != 'CUSTOMER'); 18 | contactStatusIndex: number; 19 | 20 | constructor(private contactService: ContactService, private alertService: AlertService) { } 21 | 22 | ngOnInit(): void { 23 | this.setContactStatusIndex(); 24 | } 25 | 26 | private setContactStatusIndex() { 27 | if (this.contact) { 28 | this.contactStatusIndex = this.availableContactStatuses.findIndex(status => this.contact.status === status.value); 29 | } 30 | } 31 | 32 | updateStatus(index: number) { 33 | if (index > this.contactStatusIndex) { 34 | this.alertService.clear(); 35 | 36 | let newStatus: DisplayValueMap = this.availableContactStatuses[index]; 37 | this.contact.status = newStatus.value; 38 | 39 | this.contactService.update(this.contact) 40 | .subscribe( 41 | (contact: Contact) => this.handleContactSaveResponse(contact), 42 | err => this.handleContactSaveError(err) 43 | ); 44 | } 45 | } 46 | 47 | handleContactSaveResponse(contact: Contact) { 48 | this.setContactStatusIndex(); 49 | this.alertService.success("Contact status updated!"); 50 | } 51 | 52 | handleContactSaveError(err: Error) { 53 | console.error(err); 54 | this.alertService.error("Problem updating contact status!"); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/util/phone-mask.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, HostListener, Input } from '@angular/core'; 2 | import { NgControl } from '@angular/forms'; 3 | import { AsYouType } from 'libphonenumber-js' 4 | 5 | @Directive({ 6 | selector: '[appPhoneMask]', 7 | }) 8 | export class PhoneMaskDirective { 9 | 10 | @Input('countryCode') countryCode: string; 11 | 12 | constructor(public ngControl: NgControl) { } 13 | 14 | @HostListener('ngModelChange', ['$event']) 15 | onModelChange(event) { 16 | this.onInputChange(event, false); 17 | } 18 | 19 | @HostListener('keydown.backspace', ['$event']) 20 | keydownBackspace(event) { 21 | this.onInputChange(event.target.value, true); 22 | } 23 | 24 | onInputChange(event, backspace) { 25 | let asYouType: AsYouType = this.getFormatter(); 26 | 27 | if (asYouType) { 28 | let newVal = event.replace(/\D/g, ''); 29 | 30 | if (backspace && newVal.length <= 6) { 31 | newVal = newVal.substring(0, newVal.length - 1); 32 | } 33 | 34 | newVal = asYouType.input(newVal); 35 | 36 | this.ngControl.valueAccessor.writeValue(newVal); 37 | } 38 | } 39 | 40 | private getFormatter(): AsYouType { 41 | let asYouType: AsYouType = null; 42 | 43 | switch (this.countryCode) { 44 | case 'us': 45 | asYouType = new AsYouType('US'); 46 | break; 47 | case 'jp': 48 | asYouType = new AsYouType('JP'); 49 | break; 50 | case 'ca': 51 | asYouType = new AsYouType('CA'); 52 | break; 53 | case 'ph': 54 | asYouType = new AsYouType('PH'); 55 | break; 56 | case 'mx': 57 | asYouType = new AsYouType('MX'); 58 | break; 59 | case 'be': 60 | asYouType = new AsYouType('BE'); 61 | break; 62 | case 'lu': 63 | asYouType = new AsYouType('LU'); 64 | break; 65 | case 'de': 66 | asYouType = new AsYouType('DE'); 67 | break; 68 | case 'br': 69 | asYouType = new AsYouType('BR'); 70 | break; 71 | } 72 | 73 | return asYouType; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/addresses-form/addresses-form.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewChildren, Input, ViewEncapsulation } from '@angular/core'; 2 | import { FormBuilder, FormGroup } from '@angular/forms'; 3 | import { Address } from '../../../../models/address'; 4 | import { addressTypes } from '../../../../models/address-type'; 5 | import { Contact } from '../../models/contact'; 6 | import { AddressTypeFormComponent } from './address-type-form/address-type-form.component'; 7 | 8 | @Component({ 9 | selector: 'contact-addresses-form', 10 | templateUrl: './addresses-form.component.html', 11 | styleUrls: ['./addresses-form.component.css'], 12 | encapsulation: ViewEncapsulation.None 13 | }) 14 | export class AddressesFormComponent implements OnInit { 15 | 16 | @ViewChildren(AddressTypeFormComponent) addressTypeComponents: AddressTypeFormComponent[]; 17 | 18 | @Input() contact: Contact; 19 | 20 | availableAddressTypes = addressTypes; 21 | 22 | constructor(private fb: FormBuilder) { } 23 | 24 | ngOnInit() { 25 | } 26 | 27 | populateContact(contact: Contact) { 28 | let addresses: Address[] = []; 29 | 30 | this.addressTypeComponents.forEach((element, index) => { 31 | let address = {} as Address; 32 | let addressForm: FormGroup = element.addressTypeFormGroup; 33 | 34 | address.addressType = addressForm.controls['addressType'].value; 35 | address.city = addressForm.controls['city'].value; 36 | address.country = addressForm.controls['country'].value; 37 | address.state = addressForm.controls['state'].value; 38 | address.street1 = addressForm.controls['street1'].value; 39 | address.street2 = addressForm.controls['street2'].value; 40 | address.zip = addressForm.controls['zip'].value; 41 | 42 | if ((address.city && address.city.trim().length > 0) 43 | || (address.country && address.country.trim().length > 0) 44 | || (address.state && address.state.trim().length > 0)) { 45 | 46 | addresses.push(address); 47 | } 48 | }); 49 | 50 | contact.addresses = addresses; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/features/user/email/compose-email/compose-email.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

New Email Message

4 |
5 |
6 | 7 |
8 |
9 |
10 |
11 |
To:
12 |
13 | 14 | 16 | Please enter a valid to address 17 | 18 |
19 |
20 |
21 |
Subject:
22 |
23 | 24 | 26 | Please enter a valid subject 27 | 28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 |
41 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /src/app/features/activities/edit-activity/edit-activity.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { switchMap } from 'rxjs/operators'; 3 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 4 | import { AlertService } from 'carey-alert'; 5 | import { HttpErrorResponse } from '@angular/common/http'; 6 | import { Activity } from '../models/activity'; 7 | import { ActivityService } from '../service/activity.service'; 8 | import { BreadcrumbService } from '../../../ui/breadcrumb/breadcrumb.service'; 9 | 10 | @Component({ 11 | selector: 'app-edit-activity', 12 | templateUrl: './edit-activity.component.html', 13 | styleUrls: ['./edit-activity.component.css'] 14 | }) 15 | export class EditActivityComponent implements OnInit { 16 | 17 | loading: boolean = true; 18 | activity: Activity; 19 | 20 | constructor(private route: ActivatedRoute, 21 | private alertService: AlertService, private router: Router, 22 | private activityService: ActivityService, private breadcrumbService: BreadcrumbService) { } 23 | 24 | ngOnInit(): void { 25 | this.loadActivity(); 26 | } 27 | 28 | private loadActivity() { 29 | let activity$ = this.route.queryParamMap.pipe( 30 | switchMap((params: ParamMap) => 31 | this.activityService.fetchActivityById(params.get('activityId'))) 32 | ); 33 | 34 | activity$.subscribe( 35 | (activity: Activity) => this.handleActivityResponse(activity), 36 | err => this.handleActivityError(err) 37 | ); 38 | } 39 | 40 | private handleActivityResponse(activity: Activity) { 41 | this.activity = activity; 42 | this.loading = false; 43 | this.breadcrumbService.updateBreadcrumb("Edit " + this.activity.type.name); 44 | } 45 | 46 | private handleActivityError(err: Error) { 47 | this.loading = false; 48 | console.error(err); 49 | 50 | let alertMessage: string = 'Problem loading data'; 51 | 52 | if (err instanceof HttpErrorResponse) { 53 | if (err.status) { 54 | if (err.status == 404) { 55 | alertMessage = 'Activity with that ID does not exist'; 56 | } 57 | } 58 | } 59 | 60 | this.alertService.error(alertMessage); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/app/features/activities/add-activity/add-activity.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; 2 | import { switchMap } from 'rxjs/operators'; 3 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 4 | import { AlertService } from 'carey-alert'; 5 | import { HttpErrorResponse } from '@angular/common/http'; 6 | import { Contact } from '../../contacts/models/contact'; 7 | import { ContactService } from '../../contacts/services/contact.service'; 8 | 9 | 10 | @Component({ 11 | selector: 'app-add-activity', 12 | templateUrl: './add-activity.component.html', 13 | styleUrls: ['./add-activity.component.css'] 14 | }) 15 | export class AddActivityComponent implements OnInit { 16 | 17 | contact: Contact; 18 | loading: boolean = true; 19 | 20 | constructor(private route: ActivatedRoute, 21 | private alertService: AlertService, private router: Router, 22 | private contactService: ContactService) { } 23 | 24 | ngOnInit(): void { 25 | if (this.route.snapshot.queryParams['contactId']) { 26 | this.loadContact(); 27 | } else { 28 | this.loading = false; 29 | } 30 | } 31 | 32 | private loadContact() { 33 | if (this.route.snapshot.queryParams['contactId']) { 34 | let contact$ = this.route.queryParamMap.pipe( 35 | switchMap((params: ParamMap) => 36 | this.contactService.fetchById(params.get('contactId'))) 37 | ); 38 | 39 | contact$.subscribe( 40 | (contact: Contact) => this.handleContactResponse(contact), 41 | err => this.handleContactError(err) 42 | ); 43 | } 44 | } 45 | 46 | private handleContactResponse(contact: Contact) { 47 | this.contact = contact; 48 | this.loading = false; 49 | } 50 | 51 | private handleContactError(err: Error) { 52 | this.loading = false; 53 | console.error(err); 54 | 55 | let alertMessage: string = 'Something went wrong, please call support'; 56 | 57 | if (err instanceof HttpErrorResponse) { 58 | if (err.status) { 59 | if (err.status == 404) { 60 | alertMessage = 'Contact with that ID does not exist'; 61 | } 62 | } 63 | } 64 | 65 | this.alertService.error(alertMessage); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/app/features/deals/add-deal/add-deal.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core'; 2 | import { switchMap } from 'rxjs/operators'; 3 | import { ActivatedRoute, ParamMap, Router } from '@angular/router'; 4 | import { AlertService } from 'carey-alert'; 5 | import { HttpErrorResponse } from '@angular/common/http'; 6 | import { Deal } from '../models/deal'; 7 | import { Contact } from '../../contacts/models/contact'; 8 | import { ContactService } from '../../contacts/services/contact.service'; 9 | 10 | 11 | @Component({ 12 | selector: 'app-add-deal', 13 | templateUrl: './add-deal.component.html', 14 | styleUrls: ['./add-deal.component.css'] 15 | }) 16 | export class AddDealComponent implements OnInit { 17 | 18 | contact: Contact; 19 | loading: boolean = true; 20 | 21 | constructor(private route: ActivatedRoute, 22 | private alertService: AlertService, private router: Router, 23 | private contactService: ContactService) { } 24 | 25 | ngOnInit(): void { 26 | if (this.route.snapshot.queryParams['contactId']) { 27 | this.loadContact(); 28 | } else { 29 | this.loading = false; 30 | } 31 | } 32 | 33 | private loadContact() { 34 | if (this.route.snapshot.queryParams['contactId']) { 35 | let contact$ = this.route.queryParamMap.pipe( 36 | switchMap((params: ParamMap) => 37 | this.contactService.fetchById(params.get('contactId'))) 38 | ); 39 | 40 | contact$.subscribe( 41 | (contact: Contact) => this.handleContactResponse(contact), 42 | err => this.handleContactError(err) 43 | ); 44 | } 45 | } 46 | 47 | private handleContactResponse(contact: Contact) { 48 | this.contact = contact; 49 | this.loading = false; 50 | } 51 | 52 | private handleContactError(err: Error) { 53 | this.loading = false; 54 | console.error(err); 55 | 56 | let alertMessage: string = 'Something went wrong, please call support'; 57 | 58 | if (err instanceof HttpErrorResponse) { 59 | if (err.status) { 60 | if (err.status == 404) { 61 | alertMessage = 'Contact with that ID does not exist'; 62 | } 63 | } 64 | } 65 | 66 | this.alertService.error(alertMessage); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/features/activities/activities-list/activities-list.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{title}} 4 | 5 | 6 |
7 | 8 |
9 |
10 | Problem loading activities. Please call support. 11 |
12 |
13 |
14 |
15 |
16 | {{activity.type.icon}} 17 |
18 |
19 |
20 |
21 |
22 | {{activity.title}} 23 | 24 | @ {{activity.location}} 25 | 26 |
27 |
{{activity.outcome.name}}
28 |
29 |
30 | {{activity.notes}} 31 |
32 |
33 | {{dateService.getShortDateAndTimeDisplay(activity.startDate)}} 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | There are no activities at this time. 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/app/features/activities/recent-activities-by-contact/recent-activities-by-contact.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Recent Activities 4 | 5 | 6 |
7 | 8 |
9 |
10 | Problem loading activities. Please call support. 11 |
12 |
13 |
14 |
15 |
16 | {{activity.type.icon}} 17 |
18 |
19 |
20 |
21 |
22 | {{activity.title}} 23 | 24 | @ {{activity.location}} 25 | 26 |
27 |
{{activity.outcome.name}}
28 |
29 |
30 | {{activity.notes}} 31 |
32 |
33 | {{dateService.getShortDateAndTimeDisplay(activity.startDate)}} 34 |
35 |
36 | 39 |
40 |
41 |
42 |
43 |
44 |
45 | There are no activities at this time. 46 |
47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/app/features/contacts/contact-form/contact-form.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 | 8 |
9 | 10 |
11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 |
26 | 27 | 28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 | 37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | -------------------------------------------------------------------------------- /src/app/features/contacts/services/contact.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable, of } from 'rxjs'; 4 | import { environment } from '../../../../environments/environment'; 5 | import { Contact } from '../models/contact'; 6 | import { map, switchMap, tap } from 'rxjs/operators'; 7 | import { FormGroup, ValidationErrors } from '@angular/forms'; 8 | import { BasicInfoFormComponent } from '../contact-form/basic-info-form/basic-info-form.component'; 9 | 10 | const baseUrl: string = environment.baseCustomerServiceUrl; 11 | 12 | @Injectable({ providedIn: 'root' }) 13 | export class ContactService { 14 | 15 | constructor(private http: HttpClient) { } 16 | 17 | convertContactToCustomer(contactId: string) { 18 | this.fetchById(contactId).pipe( 19 | switchMap(contact => { 20 | contact.status = 'CUSTOMER'; 21 | return this.update(contact); 22 | }) 23 | ) 24 | .subscribe( 25 | (contact: Contact) => console.log("Contact status updated successfully", contact), 26 | (err: Error) => console.error(err) 27 | ); 28 | } 29 | 30 | fetchMyContacts(): Observable { 31 | let url = `${baseUrl}/contact`; 32 | //console.log("Fetch my contacts URL is " + url); 33 | 34 | return this.http.get(url); 35 | } 36 | 37 | create(contact: Contact): Observable { 38 | let url = `${baseUrl}/contact`; 39 | //console.log("Create contact URL is " + url); 40 | 41 | return this.http.post(url, contact); 42 | } 43 | 44 | update(contact: Contact): Observable { 45 | let url = `${baseUrl}/contact/${contact.id}`; 46 | //console.log("Update contact URL is " + url); 47 | 48 | return this.http.put(url, contact); 49 | } 50 | 51 | fetchById(id: string): Observable { 52 | if (id) { 53 | let url = `${baseUrl}/contact/${id}`; 54 | //console.log("Fetch contact URL is " + url); 55 | 56 | return this.http.get(url); 57 | } else { 58 | return of(null); 59 | } 60 | } 61 | 62 | doesEmailExist(email: string): Observable { 63 | let url = `${baseUrl}/contact/emailcheck`; 64 | 65 | let content: any = {}; 66 | content.email = email; 67 | 68 | let response$: Observable = this.http.post(url, content); 69 | 70 | return response$; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/app/pipes/time-difference.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | const MILLISECONDS_IN_SECOND: number = 1000; 4 | const MILLISECONDS_IN_MINUTE: number = 60 * MILLISECONDS_IN_SECOND; 5 | const MILLISECONDS_IN_HOUR: number = MILLISECONDS_IN_MINUTE * 60; 6 | const MILLISECONDS_IN_DAY: number = MILLISECONDS_IN_HOUR * 24; 7 | const SINGLE_DAY: string = 'day'; 8 | const PLURAL_DAY: string = 'days' 9 | const SINGLE_HOUR: string = 'hour'; 10 | const PLURAL_HOUR: string = 'hours'; 11 | const SINGLE_MINUTE: string = 'minute'; 12 | const PLURAL_MINUTE: string = 'minutes'; 13 | 14 | /** 15 | * This pipe spits out the difference between two dates in readable format. 16 | * 17 | * It accepts a number that represents the difference between two dates. In milliseconds. 18 | * 19 | * If that number is negative, then the event happened in the past. 20 | * 21 | * The pipe translates the difference to something like "43 minutes ago" or "3 days ago." 22 | * */ 23 | @Pipe({ 24 | name: 'timeDifference' 25 | }) 26 | export class TimeDifferencePipe implements PipeTransform { 27 | transform(value: number): string { 28 | let display: string = "calculating..."; 29 | let tense: string = 'from now'; 30 | 31 | if (value) { 32 | if (value < 0) { 33 | tense = 'ago'; 34 | value = Math.abs(value); 35 | } 36 | 37 | if (value < MILLISECONDS_IN_MINUTE) { 38 | display = `less than a minute ${tense}`; 39 | } else if (value < MILLISECONDS_IN_HOUR) { 40 | let minutes: number = Math.floor(value / MILLISECONDS_IN_MINUTE); 41 | let units: string = (minutes == 1) ? SINGLE_MINUTE : PLURAL_MINUTE; 42 | display = `${minutes} ${units} ${tense}`; 43 | } else if (value < MILLISECONDS_IN_DAY) { 44 | let hours: number = Math.floor(value / MILLISECONDS_IN_HOUR); 45 | let minutes: number = (Math.floor((value - (hours * MILLISECONDS_IN_HOUR)) / MILLISECONDS_IN_MINUTE)); 46 | let hoursUnits: string = (hours == 1) ? SINGLE_HOUR : PLURAL_HOUR; 47 | let minutesUnits: string = (minutes == 1) ? SINGLE_MINUTE : PLURAL_MINUTE; 48 | display = `${hours} ${hoursUnits}, ${minutes} ${minutesUnits} ${tense}`; 49 | } else { 50 | let days: number = Math.floor(value / MILLISECONDS_IN_DAY); 51 | let daysUnits: string = (days == 1) ? SINGLE_DAY : PLURAL_DAY; 52 | display = `${days} ${daysUnits} ${tense}`; 53 | } 54 | } 55 | 56 | return display; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/app/features/activities/view-activity/view-activity-menu/view-activity-menu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { MatDialog } from '@angular/material/dialog'; 3 | import { Router } from '@angular/router'; 4 | import { AlertService } from 'carey-alert'; 5 | import { Activity } from '../../models/activity'; 6 | import { ActivityService } from '../../service/activity.service'; 7 | import { UpdateNotesDialog } from '../../ui/update-notes-dialog/update-notes-dialog.component'; 8 | 9 | @Component({ 10 | selector: 'app-view-activity-menu', 11 | templateUrl: './view-activity-menu.component.html', 12 | styleUrls: ['./view-activity-menu.component.css'] 13 | }) 14 | export class ViewActivityMenuComponent implements OnInit { 15 | 16 | @Input() activity: Activity; 17 | 18 | constructor(private router: Router, public dialog: MatDialog, private activityService: ActivityService, 19 | private alertService: AlertService) { } 20 | 21 | ngOnInit(): void { 22 | } 23 | 24 | complete() { 25 | this.alertService.clear(); 26 | 27 | if (this.activity) this.activity.status = 'COMPLETED'; 28 | this.updateActivity(); 29 | } 30 | 31 | cancel() { 32 | this.alertService.clear(); 33 | 34 | if (this.activity) this.activity.status = 'CANCELLED'; 35 | this.updateActivity(); 36 | } 37 | 38 | putOnHold() { 39 | this.alertService.clear(); 40 | 41 | if (this.activity) this.activity.status = 'ON_HOLD'; 42 | this.updateActivity(); 43 | } 44 | 45 | updateNotes() { 46 | this.alertService.clear(); 47 | 48 | const dialogRef = this.dialog.open(UpdateNotesDialog, { 49 | width: '350px', 50 | closeOnNavigation: true, 51 | data: this.activity.notes 52 | }); 53 | 54 | dialogRef.afterClosed().subscribe(result => { 55 | if (result) { 56 | this.activity.notes = result; 57 | this.updateActivity(); 58 | } 59 | }); 60 | } 61 | 62 | private updateActivity() { 63 | this.activityService.updateActivity(this.activity).subscribe( 64 | (activity: Activity) => this.handleActivityUpdate(activity), 65 | (err) => this.handleError(err) 66 | ); 67 | } 68 | 69 | private handleActivityUpdate(activity: Activity) { 70 | //this.alertService.success("Activity successfully updated!"); 71 | } 72 | 73 | private handleError(err: Error) { 74 | console.error(err); 75 | this.alertService.error("Problem updating, please contact support."); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "careydevelopmentcrm", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "ng test", 9 | "lint": "ng lint", 10 | "e2e": "ng e2e" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular-material-components/datetime-picker": "^5.0.3", 15 | "@angular/animations": "~11.1.1", 16 | "@angular/cdk": "^11.1.1", 17 | "@angular/common": "~11.1.1", 18 | "@angular/compiler": "~11.1.1", 19 | "@angular/core": "~11.1.1", 20 | "@angular/flex-layout": "^11.0.0-beta.33", 21 | "@angular/forms": "~11.1.1", 22 | "@angular/http": "~7.0.0", 23 | "@angular/material": "^11.1.1", 24 | "@angular/platform-browser": "~11.1.1", 25 | "@angular/platform-browser-dynamic": "~11.1.1", 26 | "@angular/router": "~11.1.1", 27 | "@mat-datetimepicker/core": "^5.1.2", 28 | "@nativescript/schematics": "^10.1.0", 29 | "@types/quill": "^1.3.10", 30 | "carey-alert": "0.0.1", 31 | "carey-auth": "^0.2.9", 32 | "carey-geo": "0.0.4", 33 | "carey-image-uploader": "0.0.2", 34 | "carey-text-utils": "0.0.3", 35 | "carey-url-util": "0.0.1", 36 | "carey-user": "^0.1.0", 37 | "carey-validation": "^0.4.3", 38 | "core-js": "^2.5.4", 39 | "echarts": "^5.0.2", 40 | "engine.io-client": "^5.1.2", 41 | "libphonenumber-js": "^1.9.6", 42 | "moment": "^2.29.1", 43 | "ngx-echarts": "^6.0.1", 44 | "ngx-flag-picker": "^11.1.0", 45 | "ngx-moment": "^5.0.0", 46 | "ngx-quill": "^13.2.0", 47 | "quill": "^1.3.6", 48 | "rxjs": "~6.6.3", 49 | "xmlhttprequest-ssl": "~1.6.1", 50 | "zone.js": "~0.10.3" 51 | }, 52 | "devDependencies": { 53 | "@angular-devkit/build-angular": "^0.1102.12", 54 | "@angular/cli": "^11.1.2", 55 | "@angular/compiler-cli": "~11.1.1", 56 | "@angular/language-service": "~11.1.1", 57 | "@types/jasmine": "~2.8.8", 58 | "@types/jasminewd2": "~2.0.3", 59 | "@types/node": "^14.14.33", 60 | "codelyzer": "~4.5.0", 61 | "jasmine-core": "~2.99.1", 62 | "jasmine-spec-reporter": "~4.2.1", 63 | "karma": "^6.3.4", 64 | "karma-chrome-launcher": "~2.2.0", 65 | "karma-coverage-istanbul-reporter": "~2.0.1", 66 | "karma-jasmine": "~1.1.2", 67 | "karma-jasmine-html-reporter": "^0.2.2", 68 | "protractor": "~5.4.0", 69 | "resize-observer-polyfill": "^1.5.1", 70 | "ts-node": "~7.0.0", 71 | "tslint": "~5.11.0", 72 | "typescript": "~4.0.3" 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/app/features/deals/stage-progress-bar/stage-progress-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { AlertService } from 'carey-alert'; 3 | import { DealStage } from '../models/deal-stage'; 4 | import { Deal } from '../models/deal'; 5 | import { DealService } from '../service/deal.service'; 6 | 7 | @Component({ 8 | selector: 'app-stage-progress-bar', 9 | templateUrl: './stage-progress-bar.component.html', 10 | styleUrls: ['./stage-progress-bar.component.css'] 11 | }) 12 | export class StageProgressBarComponent implements OnInit { 13 | 14 | @Input() deal: Deal; 15 | 16 | availableDealStages: DealStage[]; 17 | dealStageIndex: number; 18 | 19 | constructor(private dealService: DealService, private alertService: AlertService) { } 20 | 21 | ngOnInit(): void { 22 | this.loadDealStages(); 23 | } 24 | 25 | private loadDealStages() { 26 | this.dealService.fetchDealStagesBySalesType('INBOUND').subscribe( 27 | (stages: DealStage[]) => this.handleDealStages(stages), 28 | (err: Error) => this.handleDealStagesError(err) 29 | ); 30 | } 31 | 32 | private handleDealStages(stages: DealStage[]) { 33 | console.log("deal stages is ", stages); 34 | this.availableDealStages = this.dealService.removeClosingStages(stages); 35 | this.setDealStageIndex(); 36 | } 37 | 38 | private handleDealStagesError(err: Error) { 39 | console.error(err); 40 | this.alertService.error("Problem loading deal stages!"); 41 | } 42 | 43 | private setDealStageIndex() { 44 | if (this.deal && this.availableDealStages && this.availableDealStages.length > 0) { 45 | this.dealStageIndex = this.availableDealStages.findIndex(stage => this.deal.stage.id === stage.id); 46 | } 47 | } 48 | 49 | updateStage(index: number) { 50 | if (index > this.dealStageIndex) { 51 | this.alertService.clear(); 52 | 53 | let newStage: DealStage = this.availableDealStages[index]; 54 | this.deal.stage = newStage; 55 | 56 | this.dealService.updateDeal(this.deal) 57 | .subscribe( 58 | (deal: Deal) => this.handleDealSaveResponse(deal), 59 | err => this.handleDealSaveError(err) 60 | ); 61 | } 62 | } 63 | 64 | handleDealSaveResponse(deal: Deal) { 65 | this.setDealStageIndex(); 66 | this.alertService.success("Deal stage updated!"); 67 | } 68 | 69 | handleDealSaveError(err: Error) { 70 | console.error(err); 71 | this.alertService.error("Problem updating deal stage!"); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/app/util/http-error-interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse } from '@angular/common/http'; 3 | import { Observable, throwError, of } from 'rxjs'; 4 | import { catchError, retry } from 'rxjs/operators'; 5 | import { AuthenticationService } from 'carey-auth'; 6 | import { Router } from '@angular/router'; 7 | import { AlertService } from 'carey-alert'; 8 | 9 | @Injectable() 10 | export class HttpErrorInterceptor implements HttpInterceptor { 11 | 12 | constructor(private authenticationService: AuthenticationService, private router: Router, 13 | private alertService: AlertService) { } 14 | 15 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 16 | 17 | let handled: boolean = false; 18 | 19 | return next.handle(request) 20 | .pipe( 21 | //taking out retry for now because it's even retrying on bad credentials 22 | //retry(1), 23 | catchError((returnedError) => { 24 | let errorMessage = null; 25 | console.log("Returned error", returnedError.error); 26 | 27 | if (returnedError.error instanceof ErrorEvent) { 28 | errorMessage = `Error: ${returnedError.error.message}`; 29 | } else if (returnedError instanceof HttpErrorResponse) { 30 | errorMessage = `Error Status ${returnedError.status}: ${returnedError.message}`; 31 | handled = this.handleServerSideError(returnedError); 32 | } 33 | 34 | console.error(errorMessage ? errorMessage : returnedError); 35 | 36 | if (!handled) { 37 | return throwError(returnedError); 38 | } else { 39 | return of(returnedError); 40 | } 41 | }) 42 | ) 43 | } 44 | 45 | private handleServerSideError(error: HttpErrorResponse): boolean { 46 | let handled: boolean = false; 47 | 48 | switch (error.status) { 49 | case 401: 50 | //we don't want to redirect people to the login page when they're already on 51 | //the login page 52 | if (this.router.url != '/login') { 53 | this.alertService.info("Please login again.", { keepAfterRouteChange: false }); 54 | this.authenticationService.logout(); 55 | handled = true; 56 | } 57 | 58 | break; 59 | case 403: 60 | this.alertService.info("Please login again.", { keepAfterRouteChange: false }); 61 | this.authenticationService.logout(); 62 | handled = true; 63 | break; 64 | } 65 | 66 | return handled; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/app/features/ui/model/menu.ts: -------------------------------------------------------------------------------- 1 | import { NavItem } from './nav-item'; 2 | 3 | export let menu: NavItem[] = [ 4 | { 5 | displayName: 'Dashboard', 6 | iconName: 'dashboard', 7 | route: 'dashboard' 8 | }, 9 | { 10 | displayName: 'Contacts', 11 | iconName: 'group', 12 | route: 'contacts', 13 | children: [ 14 | { 15 | displayName: 'View Contacts', 16 | iconName: 'list', 17 | route: 'contacts/view-contacts' 18 | }, 19 | { 20 | displayName: 'Add Contact', 21 | iconName: 'add_box', 22 | route: 'contacts/add-contact' 23 | } 24 | ] 25 | }, 26 | { 27 | displayName: 'Accounts', 28 | iconName: 'business', 29 | route: 'accounts', 30 | children: [ 31 | { 32 | displayName: 'View Accounts', 33 | iconName: 'list', 34 | route: 'accounts/view-accounts' 35 | }, 36 | { 37 | displayName: 'Add Account', 38 | iconName: 'add_box', 39 | route: 'accounts/add-account' 40 | } 41 | ] 42 | }, 43 | { 44 | displayName: 'Activities', 45 | iconName: 'grading', 46 | route: 'activities', 47 | children: [ 48 | { 49 | displayName: 'View Activities', 50 | iconName: 'list', 51 | route: 'activities/view-activities' 52 | }, 53 | { 54 | displayName: 'Add Activity', 55 | iconName: 'add_box', 56 | route: 'activities/add-activity' 57 | } 58 | ] 59 | }, 60 | { 61 | displayName: 'Deals', 62 | iconName: 'paid', 63 | route: 'deals', 64 | children: [ 65 | { 66 | displayName: 'View Deals', 67 | iconName: 'list', 68 | route: 'deals/view-deals' 69 | }, 70 | { 71 | displayName: 'Add Deal', 72 | iconName: 'add_box', 73 | route: 'deals/add-deal' 74 | } 75 | ] 76 | }, 77 | { 78 | displayName: 'User', 79 | iconName: 'face', 80 | route: 'user', 81 | children: [ 82 | { 83 | displayName: 'Email', 84 | iconName: 'markunread_mailbox', 85 | route: 'user/email/inbox' 86 | }, 87 | { 88 | displayName: 'Account Info', 89 | iconName: 'account_box', 90 | route: 'user/account-info' 91 | }, 92 | { 93 | displayName: 'Profile Image', 94 | iconName: 'image', 95 | route: 'user/profile-image' 96 | } 97 | ] 98 | }, 99 | { 100 | displayName: 'Sign Out', 101 | iconName: 'highlight_off' 102 | } 103 | ]; 104 | --------------------------------------------------------------------------------