diff --git a/client/components/import/import.jade b/client/components/import/import.jade index 7b55dadbd..ed42fe44b 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -56,17 +56,17 @@ template(name="importMapMembersAddPopup") p | {{_ 'import-user-select'}} .js-map-member - +EasySearch.Input(index=searchIndex) + input.js-search-member-input(type="text" placeholder="{{_ 'search-users'}}") ul.pop-over-list - +EasySearch.Each(index=searchIndex) + each searchResults li.item.js-member-item - a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{__originalId}}") - +userAvatar(userId=__originalId) + a.name.js-select-import(title="{{profile.fullname}} ({{username}})" data-id="{{_id}}") + +userAvatar(userId=_id) span.full-name = profile.fullname | ({{username}}) - +EasySearch.IfSearching(index=searchIndex) + if searching.get +spinner - +EasySearch.IfNoResults(index=searchIndex) + if noResults.get .manage-member-section p.quiet {{_ 'no-results'}} diff --git a/client/components/import/import.js b/client/components/import/import.js index 4d4ba7fa7..757b55e41 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -311,6 +311,73 @@ BlazeComponent.extendComponent({ }, }).register('importMapMembersAddPopup'); +// Global reactive variables for import member popup +const importMemberPopupState = { + searching: new ReactiveVar(false), + searchResults: new ReactiveVar([]), + noResults: new ReactiveVar(false), + searchTimeout: null +}; + +BlazeComponent.extendComponent({ + onCreated() { + // Use global state + this.searching = importMemberPopupState.searching; + this.searchResults = importMemberPopupState.searchResults; + this.noResults = importMemberPopupState.noResults; + this.searchTimeout = importMemberPopupState.searchTimeout; + }, + + onRendered() { + this.find('.js-search-member-input').focus(); + }, + + performSearch(query) { + if (!query || query.length < 2) { + this.searchResults.set([]); + this.noResults.set(false); + return; + } + + this.searching.set(true); + this.noResults.set(false); + + const results = UserSearchIndex.search(query, { limit: 20 }).fetch(); + this.searchResults.set(results); + this.searching.set(false); + + if (results.length === 0) { + this.noResults.set(true); + } + }, + + events() { + return [ + { + 'keyup .js-search-member-input'(event) { + const query = event.target.value.trim(); + + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + + this.searchTimeout = setTimeout(() => { + this.performSearch(query); + }, 300); + }, + }, + ]; + }, +}).register('importMapMembersAddPopupSearch'); + Template.importMapMembersAddPopup.helpers({ - searchIndex: () => UserSearchIndex, + searchResults() { + return importMemberPopupState.searchResults.get(); + }, + searching() { + return importMemberPopupState.searching; + }, + noResults() { + return importMemberPopupState.noResults; + } }) diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index d63ea9d98..023b01b9e 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -631,7 +631,7 @@ template(name="removeBoardTeamPopup") template(name="addMemberPopup") .js-search-member - +EasySearch.Input(index=searchIndex) + input.js-search-member-input(type="text" placeholder="{{_ 'email-address'}}") if loading.get +spinner @@ -639,25 +639,38 @@ template(name="addMemberPopup") .warning {{_ error.get}} else ul.pop-over-list - +EasySearch.Each(index=searchIndex) + each searchResults li.item.js-member-item(class="{{#if isBoardMember}}disabled{{/if}}") a.name.js-select-member(title="{{profile.fullname}} ({{username}})") - +userAvatar(userId=__originalId) + +userAvatar(userId=_id) span.full-name = profile.fullname | ({{username}}) if isBoardMember .quiet ({{_ 'joined'}}) - +EasySearch.IfSearching(index=searchIndex) + if searching.get +spinner - +EasySearch.IfNoResults(index=searchIndex) + if noResults.get .manage-member-section p.quiet {{_ 'no-results'}} button.js-email-invite.primary.full {{_ 'email-invite'}} +template(name="addMemberPopupTest") + .js-search-member + input.js-search-member-input(type="text" placeholder="{{_ 'email-address'}}") + ul.pop-over-list + each searchResults + li.item.js-member-item + a.name.js-select-member(title="{{profile.fullname}} ({{username}})") + +userAvatar(userId=_id) + span.full-name + = profile.fullname + | ({{username}}) + button.js-email-invite.primary.full {{_ 'email-invite'}} + template(name="changePermissionsPopup") ul.pop-over-list li diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 9f1d9c5c1..273df70a5 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1492,19 +1492,28 @@ BlazeComponent.extendComponent({ }, }).register('boardCardSettingsPopup'); +// Use Session variables instead of global ReactiveVars +Session.setDefault('addMemberPopup.searchResults', []); +Session.setDefault('addMemberPopup.searching', false); +Session.setDefault('addMemberPopup.noResults', false); +Session.setDefault('addMemberPopup.loading', false); +Session.setDefault('addMemberPopup.error', ''); + +console.log('addMemberPopup Session variables initialized'); + BlazeComponent.extendComponent({ onCreated() { - this.error = new ReactiveVar(''); - this.loading = new ReactiveVar(false); + // Use Session variables + this.searchTimeout = null; }, onRendered() { - this.find('.js-search-member input').focus(); + this.find('.js-search-member-input').focus(); this.setLoading(false); }, isBoardMember() { - const userId = this.currentData().__originalId; + const userId = this.currentData()._id; const user = ReactiveCache.getUser(userId); return user && user.isBoardMember(); }, @@ -1514,15 +1523,35 @@ BlazeComponent.extendComponent({ }, setError(error) { - this.error.set(error); + Session.set('addMemberPopup.error', error); }, setLoading(w) { - this.loading.set(w); + Session.set('addMemberPopup.loading', w); }, isLoading() { - return this.loading.get(); + return Session.get('addMemberPopup.loading'); + }, + + performSearch(query) { + if (!query || query.length < 2) { + Session.set('addMemberPopup.searchResults', []); + Session.set('addMemberPopup.noResults', false); + return; + } + + Session.set('addMemberPopup.searching', true); + Session.set('addMemberPopup.noResults', false); + + // Use the fallback search + const results = UserSearchIndex.search(query, { limit: 20 }).fetch(); + Session.set('addMemberPopup.searchResults', results); + Session.set('addMemberPopup.searching', false); + + if (results.length === 0) { + Session.set('addMemberPopup.noResults', true); + } }, inviteUser(idNameEmail) { @@ -1540,18 +1569,30 @@ BlazeComponent.extendComponent({ events() { return [ { - 'keyup input'() { + 'keyup .js-search-member-input'(event) { this.setError(''); + const query = event.target.value.trim(); + this.searchQuery.set(query); + + // Clear previous timeout + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + + // Debounce search + this.searchTimeout = setTimeout(() => { + this.performSearch(query); + }, 300); }, 'click .js-select-member'() { - const userId = this.currentData().__originalId; + const userId = this.currentData()._id; const currentBoard = Utils.getCurrentBoard(); if (!currentBoard.hasMember(userId)) { this.inviteUser(userId); } }, 'click .js-email-invite'() { - const idNameEmail = $('.js-search-member input').val(); + const idNameEmail = $('.js-search-member-input').val(); if (idNameEmail.indexOf('@') < 0 || this.isValidEmail(idNameEmail)) { this.inviteUser(idNameEmail); } else this.setError('email-invalid'); @@ -1562,7 +1603,35 @@ BlazeComponent.extendComponent({ }).register('addMemberPopup'); Template.addMemberPopup.helpers({ - searchIndex: () => UserSearchIndex, + searchResults() { + const results = Session.get('addMemberPopup.searchResults'); + console.log('searchResults helper called, returning:', results); + return results; + }, + searching() { + return Session.get('addMemberPopup.searching'); + }, + noResults() { + return Session.get('addMemberPopup.noResults'); + }, + loading() { + return Session.get('addMemberPopup.loading'); + }, + error() { + return Session.get('addMemberPopup.error'); + }, + isBoardMember() { + const userId = this._id; + const user = ReactiveCache.getUser(userId); + return user && user.isBoardMember(); + } +}) + +Template.addMemberPopupTest.helpers({ + searchResults() { + console.log('addMemberPopupTest searchResults helper called'); + return Session.get('addMemberPopup.searchResults') || []; + } }) BlazeComponent.extendComponent({ diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index dd01adcd0..212c8428f 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -385,6 +385,7 @@ "editNotificationPopup-title": "Edit Notification", "editProfilePopup-title": "Edit Profile", "email": "Email", + "email-address": "Email Address", "email-enrollAccount-subject": "An account created for you on __siteName__", "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", "email-fail": "Sending email failed",