| \\n | Name | \\nPhone | \\nMobile | \\nTitle | \\n|
|---|---|---|---|---|---|
| \\n | ${contact.displayName} | \\n${contact.emails[0]} | \\n${contact.phones[0]} | \\n${contact.mobiles[0]} | \\n${contact.workingTitle} | \\n
${text}
\\n\\n\";","module.exports = \"\\n| \\n | Created | \\nFile name | \\nHours available | \\n
|---|---|---|---|
| \\n | ${exportContactFile.createdTime | dateFormat:'DD.MM.YYYY HH:mm:ss'} | \\n${getFileName(exportContactFile.blobName)} | \\n${exportContactFile.hoursAvailable} | \\n
No files available for download - click on the button above to initiate an export.
\\nCould not authenticate with the provided token or session.
\";\n }\n } catch (err) {\n console.error(\"Aurelia startup or authentication process failed:\", err);\n // General error during startup or the auth process itself\n document.body.innerHTML = `An error occurred during startup: ${err.message}
`;\n // Reject the main promise to indicate overall failure\n rejectAuthPromise(`Startup/Auth Process Error: ${err.message}`);\n }\n };\n\n // Check if we can use existing session storage\n let initialSessionIsValid = false;\n try {\n const storedStateString = sessionStorage.getItem(\"state-service\");\n const token = sessionStorage.getItem(\"token\");\n if (storedStateString && token) {\n const parsedState = JSON.parse(storedStateString);\n // Check for the essential pieces needed for a refresh attempt\n if (parsedState && parsedState.refreshToken && parsedState.customerKey) {\n initialSessionIsValid = true;\n }\n }\n } catch (e) {\n console.error(\"Failed to parse initial state from sessionStorage\", e);\n sessionStorage.clear(); // Clear potentially corrupted state\n }\n\n if (initialSessionIsValid) {\n console.log(\n \"Valid session state found in sessionStorage. Attempting refresh.\",\n );\n resolveAuthPromise(\"refresh\"); // Proceed with refresh attempt\n } else {\n console.log(\n \"No valid session state found or session is incomplete. Waiting for external token.\",\n );\n // Clear any potentially partial/invalid session state before waiting\n if (sessionStorage.length > 0) {\n console.log(\"Clearing potentially incomplete session storage.\");\n sessionStorage.clear();\n }\n\n // Set up listener for the external token\n const setPuzzelToken = async (message: MessageEvent) => {\n // Basic validation of the message structure\n if (message.data && message.data.SessionId && message.data.CustomerKey) {\n console.log(\"External token received from origin:\", message.origin);\n const receivedRefreshToken = message.data;\n console.log(\n \"Token set for customerKey: \" + receivedRefreshToken.CustomerKey,\n );\n\n // Store necessary info directly into sessionStorage under \"state-service\"\n // This will be read by authenticateAndStart when cause is 'external'\n const stateDataForAuth = {\n refreshToken: receivedRefreshToken,\n customerKey: receivedRefreshToken.CustomerKey,\n // Add other minimal essential fields if auth.load requires them immediately\n searchString: \"\",\n };\n try {\n sessionStorage.setItem(\n \"state-service\",\n JSON.stringify(stateDataForAuth),\n );\n // Explicitly remove any old access token that might conflict\n sessionStorage.removeItem(\"token\");\n } catch (e) {\n console.error(\n \"Failed to write external token state to sessionStorage\",\n e,\n );\n rejectAuthPromise(\"Failed to store external token\");\n PLATFORM.removeEventListener(\"message\", setPuzzelToken); // Clean up listener on error\n return;\n }\n\n PLATFORM.removeEventListener(\"message\", setPuzzelToken); // Remove listener after processing\n resolveAuthPromise(\"external\"); // Signal that we received the external token\n } else {\n // Log unexpected messages if needed for debugging\n // console.log(\"Received message, but it doesn't contain expected token structure.\", message.data);\n }\n };\n PLATFORM.addEventListener(\"message\", setPuzzelToken);\n\n // Set up a timeout for receiving the external token\n const TIMEOUT_MS = 15000; // 15 seconds\n const timeoutHandle = setTimeout(() => {\n console.error(\n `Timeout: No token received via postMessage within ${TIMEOUT_MS / 1000} seconds.`,\n );\n PLATFORM.removeEventListener(\"message\", setPuzzelToken); // Clean up listener\n rejectAuthPromise(\"timeout\"); // Reject the promise due to timeout\n document.body.innerHTML =\n \"Failed to receive authentication token from the parent window.
\";\n }, TIMEOUT_MS);\n\n // Ensure the timeout is cleared if the token arrives or another error occurs first\n authPromise.finally(() => clearTimeout(timeoutHandle));\n }\n\n // Wait for the authPromise to resolve (with \"refresh\" or \"external\") or reject, then proceed\n authPromise\n .then(authenticateAndStart) // Calls authenticateAndStart with the determined cause\n .catch((errorReason) => {\n // This catches rejections from the promise (timeout, explicit rejections)\n console.error(\"Authentication process failed:\", errorReason);\n // Avoid duplicating the timeout message if that was the cause\n if (\n errorReason !== \"timeout\" &&\n !document.body.innerHTML.includes(\"Could not complete authentication: ${errorReason}. Please try reloading.
`;\n }\n // Potentially log errorReason to an error reporting service\n });\n\n // --- End Revised Startup Logic ---\n}\n","export class ColumnMappings {\n public Title?: any = undefined;\n public FirstName?: any = undefined;\n public MiddleName?: any = undefined;\n public LastName: string = undefined;\n public ExternalId?: any = undefined;\n public Phone1: string = undefined;\n public Phone1Description?: any = undefined;\n public Phone2?: any = undefined;\n public Phone2Description?: any = undefined;\n public Email1: string = undefined;\n public Email1Description?: any = undefined;\n public Email2?: any = undefined;\n public Email2Description?: any = undefined;\n public FacebookId?: string = null;\n public TwitterId?: string = null;\n public TrustpilotId?: string = null;\n public WorkingTitle?: any = undefined;\n public Organization?: any = undefined;\n public Department?: any = undefined;\n public ExternalLink?: any = undefined;\n public StreetName?: any = undefined;\n public StreetNumber?: any = undefined;\n public ZipCode?: any = undefined;\n public ZipLocation?: any = undefined;\n public CountryCode?: any = undefined;\n}\n","export class Column {\n public GetPrettyName(propertyName: string) {\n switch (propertyName) {\n case \"FirstName\":\n return \"First name\";\n case \"MiddleName\":\n return \"Middle name\";\n case \"LastName\":\n return \"Last name\";\n case \"ExternalId\":\n return \"External Id\";\n case \"Phone1\":\n return \"Phone\";\n case \"Phone1Description\":\n return \"Phone description\";\n case \"Phone2\":\n return \"Mobile\";\n case \"Phone2Description\":\n return \"Mobile description\";\n case \"Email1\":\n return \"Email 1\";\n case \"Email1Description\":\n return \"Email 1 description\";\n case \"Email2\":\n return \"Email 2\";\n case \"Email2Description\":\n return \"Email 2 description\";\n case \"WorkingTitle\":\n return \"Working title\";\n case \"StreetName\":\n return \"Street name\";\n case \"StreetNumber\":\n return \"Street number\";\n case \"ZipCode\":\n return \"Zip code\";\n case \"ZipLocation\":\n return \"Zip location\";\n case \"CountryCode\":\n return \"Country code\";\n case \"ExternalLink\":\n return \"External link\";\n case \"FacebookId\":\n return \"Facebook ID\";\n case \"TwitterId\":\n return \"Twitter ID\";\n case \"TrustpilotId\":\n return \"Trustpilot ID\";\n default:\n return propertyName;\n }\n }\n}\n","import { StatusModel } from \"./status-model\";\nimport { ColumnMappings } from \"./column-mappings\";\n\nexport class ContactImportMetadata {\n public CustomerNo: number;\n public WorkbookFiles: FileList;\n public Sheets: string[];\n public WorkbookFileType: number;\n public BlobLocation: string;\n public DefaultCountryCode: string = \"NO\";\n public SheetName: string;\n public HasHeaders: boolean = true;\n public ImportMode: number = 1;\n public ColumnMappings: ColumnMappings = new ColumnMappings();\n public Headers: string[];\n public Contacts: any[];\n public StartImportCompleted: boolean;\n public Status: StatusModel = new StatusModel();\n public Version: number = 2;\n public ContactCount: number;\n public ImportSource: number;\n}\n","import {ColumnMappings} from \"./column-mappings\";\nimport {FtpSettings} from \"./ftp-settings\";\n\nexport class ContactManagementSettings {\n public importType: string;\n public columnMapping: ColumnMappings;\n public ftpSettings?: FtpSettings;\n public defaultLanguage: string;\n}\n","import { IAddress, INote, IContact, IContactIdentifier } from \"interfaces/igraph-response\";\n\nexport class ContactModel {\n public id: number;\n public type: string;\n public title: string;\n public firstName: string;\n public middleName: string;\n public lastName: string;\n public displayName: string;\n public phones: string[] = [];\n public mobiles: string[] = [];\n public emails: string[] = [];\n public organization: string;\n public department: string;\n public workingTitle: string;\n public externalLink: string;\n public lastModified: Date;\n public externalId: string;\n public contactIdentifiers: IContactIdentifier[];\n public addresses: IAddress[];\n public notes: INote[];\n\n constructor(contact: IContact = null) {\n if (contact) {\n this.id = contact.id;\n this.type = contact.type;\n this.title = contact.title;\n this.firstName = contact.firstName;\n this.middleName = contact.middleName;\n this.lastName = contact.lastName;\n this.organization = contact.organization;\n this.department = contact.department;\n this.workingTitle = contact.workingTitle;\n this.contactIdentifiers = contact.contactIdentifiers;\n this.addresses = contact.addresses;\n this.notes = contact.notes;\n this.lastModified = contact.lastModified;\n this.externalId = contact.externalId;\n this.externalLink = contact.externalLink;\n }\n }\n}\n","export class ErrorDialogContent {\n public Title: string;\n public WindowTitle: string;\n public Message1: string;\n public Message2: string;\n}\n","export class SearchEvent {\n constructor(\n public readonly searchText: string) {\n }\n}\n","import { StatusModel } from \"./status-model\";\nimport { ColumnMappings } from \"./column-mappings\";\nimport { ContactImportMetadata } from \"./contact-import-metadata\";\nimport {FtpSettings} from \"./ftp-settings\";\n\nexport class FtpContactImportMetadata extends ContactImportMetadata{\n public SheetName: string = \"Sheet1\";\n public FtpSettings: FtpSettings = new FtpSettings();\n}\n","export class FtpSettings {\n public FileType: string = \"Csv\";\n public FileName: string;\n public Delimiter: string = \";\";\n public SheetName: string;\n public HasHeaders: boolean;\n public ImportSource: number;\n public Version: number = 2;\n public WorkbookFileType: number;\n public ImportMode: number = 0;\n public ImportModeText: string = \"Full replace\";\n}\n","export class GraphQlError extends Error {\n constructor(\n public readonly status: number,\n public readonly statusText: string) {\n super();\n }\n}\n","export class StatusModel {\n public blobLocation: string;\n public successRows: number;\n public errorRows: number;\n public statusMessage: string;\n public completed: boolean;\n}\n","import { customAttribute, inject, bindable } from \"aurelia-framework\";\nimport * as $ from \"jquery\";\nimport \"bootstrap\";\n\n@customAttribute(\"bootstrap-tooltip\")\n@inject(Element)\nexport class BootstrapTooltip {\n public element: any;\n @bindable public title: any; // TODO: Add binding support\n constructor(element) {\n this.element = element;\n }\n\n public bind(bindingContext, overrideContext) {\n $(this.element).tooltip({\n delay: { show: 500, hide: 100 },\n trigger: \"hover\"\n }).attr(\"data-original-title\", this.title);\n }\n\n public unbind() {\n $(this.element).tooltip(\"dispose\");\n }\n\n public titleChanged(newValue) {\n // if attribute [aria-describedby] is present the tooltip is active\n const tooltipActive = $(this.element).attr(\"aria-describedby\");\n const action = tooltipActive ? \"show\" : \"hide\";\n $(this.element).tooltip(\"hide\")\n .attr(\"data-original-title\", newValue)\n .tooltip(action);\n }\n}\n","import { bindable, bindingMode } from \"aurelia-framework\";\n\nexport class CheckBox {\n @bindable public label: string;\n @bindable({ defaultBindingMode: bindingMode.twoWay }) public checked: boolean;\n @bindable public disabled = true;\n}\n","import { autoinject, bindable } from \"aurelia-framework\";\nimport { Router } from \"aurelia-router\";\n\n@autoinject\nexport class ContactsListPagination {\n @bindable private queryContactFunction: any;\n @bindable private totalSearchCount: number;\n @bindable private searchString: string;\n @bindable private page: number;\n @bindable private showPageFrom: number;\n @bindable private maxPaginatorLinks: number;\n @bindable private totalPages: number;\n @bindable private maxLimit: number;\n\n constructor(private readonly router: Router) { }\n\n public onPaginationClick(searchString: string, limit: number, page: number) {\n this.queryContactFunction({ searchString, limit, page });\n }\n}\n","import { autoinject, bindable } from \"aurelia-framework\";\nimport { Router } from \"aurelia-router\";\nimport { ContactModel } from \"models/contact-model\";\n\n@autoinject\nexport class ContactsListView {\n @bindable public contacts: ContactModel[];\n @bindable public headertooltip: string;\n @bindable public selectcontactfunction: any;\n @bindable public totalContacts: number;\n\n constructor(private readonly router: Router) { }\n\n public onContactClick(contact: ContactModel) {\n this.selectcontactfunction({ contact });\n }\n}\n","import { ErrorDialogContent } from \"./../../../models/error-dialog-content\";\nimport { bindable, autoinject } from \"aurelia-framework\";\nimport { DialogController } from \"aurelia-dialog\";\n\n@autoinject\n\nexport class ErrorDialog {\n @bindable public errorDialogContent: ErrorDialogContent;\n\n constructor(public controller: DialogController, ) {\n this.controller = controller;\n\n controller.settings.centerHorizontalOnly = true;\n }\n public activate(errorDialogContent) {\n this.errorDialogContent = errorDialogContent;\n }\n}\n","import * as nprogress from \"nprogress\";\nimport {bindable, noView} from \"aurelia-framework\";\n\n@noView()\nexport class LoadingIndicator {\n @bindable public loading = false;\n\n public loadingChanged(newValue) {\n if (newValue) {\n nprogress.start();\n } else {\n nprogress.done();\n }\n }\n}\n","import { StateService } from \"./../../../services/state-service\";\nimport { SearchEvent } from \"./../../../models/events/search-event\";\nimport { autoinject } from \"aurelia-framework\";\nimport { Router } from \"aurelia-router\";\nimport { EventAggregator } from \"aurelia-event-aggregator\";\nimport { ContactService } from \"services/contact-service\";\n\n@autoinject\nexport class NavigationMenu {\n\n constructor(\n private readonly router: Router,\n private readonly stateService: StateService,\n private readonly eventAggregator: EventAggregator,\n private readonly contactService: ContactService) { }\n\n public navigateTo(path: string) {\n switch (path) {\n default:\n this.router.navigateToRoute(path);\n break;\n case \"home\":\n this.router.navigateToRoute(\"home\");\n break;\n }\n }\n public search(searchText: string) {\n this.eventAggregator.publish(new SearchEvent(searchText));\n }\n}\n","import { bindable, autoinject } from \"aurelia-framework\";\nimport { Router } from \"aurelia-router\";\n\n@autoinject\nexport class PuzzelButton {\n @bindable public text: string;\n @bindable public buttonFunction: any;\n\n constructor(private readonly router: Router) { }\n\n public click() {\n this.buttonFunction();\n }\n}\n","import { bindable, observable } from \"aurelia-framework\";\n\nexport class SearchInput {\n @bindable public searchFunction: any;\n @bindable public placeholderText = \"Search for contacts\";\n @bindable public isDisabled = false;\n @bindable public iconClass: string = \"fa fa-search\";\n @observable public searchText: string;\n\n public search() {\n this.searchFunction({ searchText: this.searchText });\n }\n}\n","import { ContactExportMetadata } from \"./../../../models/contact-export-metadata\";\nimport { autoinject, LogManager } from \"aurelia-framework\";\nimport { BackendService } from \"services/backend-service\";\nimport { StateService } from \"services/state-service\";\n\n@autoinject\nexport class SettingsExport {\n public readonly log = LogManager.getLogger(\"contact-management-web:settings-export\");\n public statusMessage = \"\";\n public exportContactFiles: ContactExportMetadata[] = [];\n public refreshInterval: any;\n\n constructor(\n private stateService: StateService,\n private backendService: BackendService) {\n }\n\n public async attached() {\n await this.getExportListsAsync();\n this.refreshInterval = setInterval(() => this.getExportListsAsync(), 20000);\n }\n\n public detached() {\n clearInterval(this.refreshInterval);\n }\n\n public async initiateExportAsync() {\n\n try {\n const status = await this.backendService.exportContacts(this.stateService.customerKey);\n this.statusMessage = \"Export initiated - the file will soon be available in the list below.\";\n } catch (error) {\n this.log.error(error);\n this.statusMessage = \"A problem occured initiating the export - please try again.\";\n }\n }\n\n public async getExportListsAsync() {\n try {\n this.log.info(\"Getting list\");\n this.exportContactFiles = await this.backendService.getListOfContactExports(this.stateService.customerKey);\n this.statusMessage = \"\";\n } catch (error) {\n this.log.error(error);\n }\n }\n\n public async onFileClickAsync(blobUri: string) {\n try {\n this.statusMessage = \"Downloading file - please wait.\";\n\n const filename = this.getFileName(blobUri);\n\n const response =\n await this.backendService.downloadContactFile(this.stateService.customerKey, filename);\n\n const url = window.URL.createObjectURL(response);\n\n const anchor = document.createElement(\"a\");\n anchor.setAttribute(\"href\", url);\n anchor.setAttribute(\"download\", filename);\n anchor.click();\n window.URL.revokeObjectURL(url);\n\n this.statusMessage = \"\";\n } catch (error) {\n this.log.info(error);\n this.statusMessage = \"\";\n }\n }\n\n private getFileName(blobUri: string) {\n const startPosition: number = blobUri.lastIndexOf(\"/\") + 1;\n const lengthOfUri: number = blobUri.length;\n return blobUri.substr(startPosition, lengthOfUri - startPosition);\n }\n\n}\n","import { autoinject, LogManager } from \"aurelia-framework\";\nimport { Column } from \"models/column\";\nimport { FtpContactImportMetadata } from \"models/ftp-contact-import-metadata\";\nimport { ErrorDialogContent } from \"models/error-dialog-content\";\nimport { ErrorDialog } from \"resources/elements/error-dialog/error-dialog\";\nimport { DialogService } from \"aurelia-dialog\";\nimport { BackendService } from \"services/backend-service\";\nimport { ContactManagementSettings } from \"models/contact-management-settings\";\n\n@autoinject\nexport class SettingsFtp {\n public readonly log = LogManager.getLogger(\"contact-management-web:settings-ftp\");\n public column: Column = new Column();\n public ftpContactImportMetadata: FtpContactImportMetadata = new FtpContactImportMetadata();\n public contactManagementSettings: ContactManagementSettings = new ContactManagementSettings();\n public columnsCount: number;\n public errorDialogContent: ErrorDialogContent;\n public statusMessage = \"\";\n private tooltipFtpSettings: string = \"Map columns from the Excel file to each field. For example External Id to the Excel column id.