From 6d3dd0d3b28a5376d1ce988beb42bbb43d87df71 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Mon, 12 Feb 2018 17:38:15 +0000 Subject: [PATCH 1/4] location search --- public/simple/index.html | 6 +- src/components/App.js | 4 + src/components/contractors/Contractors.js | 82 +++++++++++++------- src/components/contractors/Filters.js | 42 ++++++++++ src/components/contractors/SelectSubjects.js | 29 ------- src/index.js | 7 +- src/main.scss | 2 +- src/styles/contractor-filters.scss | 81 +++++++++++++++++++ src/styles/contractors.scss | 16 ---- src/styles/subject-select.scss | 36 --------- 10 files changed, 195 insertions(+), 110 deletions(-) create mode 100644 src/components/contractors/Filters.js delete mode 100644 src/components/contractors/SelectSubjects.js create mode 100644 src/styles/contractor-filters.scss delete mode 100644 src/styles/subject-select.scss diff --git a/public/simple/index.html b/public/simple/index.html index 68defc8f..75e7e15a 100644 --- a/public/simple/index.html +++ b/public/simple/index.html @@ -34,6 +34,10 @@ diff --git a/src/components/App.js b/src/components/App.js index 99d6bb5e..99b177df 100644 --- a/src/components/App.js +++ b/src/components/App.js @@ -30,6 +30,10 @@ class App extends Component { get_text (name, replacements) { let s = this.props.config.messages[name] + if (!s) { + console.warn(`not translation found for "${name}"`) + return name + } for (let [k, v] of Object.entries(replacements || {})) { s = s.replace(`{${k}}`, v) } diff --git a/src/components/contractors/Contractors.js b/src/components/contractors/Contractors.js index 8d715bb7..00760d49 100644 --- a/src/components/contractors/Contractors.js +++ b/src/components/contractors/Contractors.js @@ -4,19 +4,19 @@ import {async_start, slugify} from '../../utils' import {If} from '../shared/Tools' import {Grid, List} from './List' import ConModal from './ConModal' -import SelectSubjects from './SelectSubjects' +import {SubjectSelect, LocationInput} from './Filters' class Contractors extends Component { constructor (props) { super(props) this.state = { - contractors: [], - got_contractors: false, + contractor_response: null, page: 1, more_pages: false, subjects: [], selected_subject: null, last_url: null, + location_str: null, } this.update_contractors = this.update_contractors.bind(this) this.get_contractor_details = this.get_contractor_details.bind(this) @@ -24,6 +24,8 @@ class Contractors extends Component { this.subject_url = this.subject_url.bind(this) this.page_url = this.page_url.bind(this) this.subject_change = this.subject_change.bind(this) + this.location_change = this.location_change.bind(this) + this.submit_location = this.submit_location.bind(this) } async componentDidMount () { @@ -59,7 +61,15 @@ class Contractors extends Component { this.update_contractors(selected_subject) } - async update_contractors (selected_subject) { + location_change (loc) { + this.setState({location_str: loc}) + } + + submit_location (location_str) { + this.update_contractors(this.state.selected_subject, location_str) + } + + async update_contractors (selected_subject, location_str) { if (!selected_subject) { const m = this.props.history.location.pathname.match(/subject\/(\d+)/) const subject_id = m ? parseInt(m[1], 10) : null @@ -67,6 +77,9 @@ class Contractors extends Component { selected_subject = this.state.subjects.find(s => s.id === subject_id) } } + if (location_str === undefined) { + location_str = this.state.location_str + } const m = this.props.history.location.pathname.match(/page\/(\d+)/) const page = m ? parseInt(m[1], 10) : 1 @@ -75,20 +88,15 @@ class Contractors extends Component { subject: selected_subject ? selected_subject.id : null, pagination: this.props.config.pagination, page: page, + location: location_str, }) - const data = await this.props.root.requests.get('contractors', args) - let contractors - if (Array.isArray(data)) { - contractors = data - } else { - contractors = data.results - } - this.props.config.event_callback('updated_contractors', contractors) - this.setState({contractors: []}) + console.log(args) + const contractor_response = await this.props.root.requests.get('contractors', args) + this.props.config.event_callback('updated_contractors', contractor_response) + this.setState({contractor_response: {results: []}}) setTimeout(() => this.setState({ - contractors, - got_contractors: true, - more_pages: contractors.length === this.props.config.pagination, + contractor_response, + more_pages: contractor_response.count > contractor_response.results.length, }), 0) } @@ -109,18 +117,40 @@ class Contractors extends Component { } render () { + let description = '' + const con_count = this.state.contractor_response && this.state.contractor_response.count + if (con_count && this.state.selected_subject) { + const msg_id_suffix = con_count === 1 ? 'single' : 'plural' + description = this.props.root.get_text('subject_filter_summary_' + msg_id_suffix, { + count: con_count, + subject: this.state.selected_subject.name, + }) + } const DisplayComponent = this.props.config.mode === 'grid' ? Grid : List return (
- - + +
+ + + +
+
+ {description} +
- - + +
{this.props.root.get_text('no_tutors_found')}
@@ -145,8 +175,8 @@ class Contractors extends Component { ( { + return ( +
+ + loc_change(v.target.value || null)} + onKeyPress={v => v.key === 'Enter' && submit()} + placeholder={get_text('location_input_placeholder')}/> + loc_change(null) || submit(null)}> + × + + +
+ ) +} + diff --git a/src/components/contractors/SelectSubjects.js b/src/components/contractors/SelectSubjects.js deleted file mode 100644 index 0920e1f4..00000000 --- a/src/components/contractors/SelectSubjects.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import Select from 'react-select' -import 'react-select/dist/react-select.css' - -const SelectSubject = ({get_text, contractors, subjects, selected_subject, subject_change}) => { - let description = '' - if (selected_subject) { - const msg_id_suffix = contractors.length === 1 ? 'single' : 'plural' - description = get_text('subject_filter_summary_' + msg_id_suffix, { - count: contractors.length, - subject: selected_subject.name, - }) - } - return [ -
- loc_change(v.target.value || null)} - onKeyPress={v => v.key === 'Enter' && submit()} - placeholder={get_text('location_input_placeholder')}/> - loc_change(null) || submit(null)}> - × - +
+ loc_change(v.target.value || null)} + onKeyPress={v => v.key === 'Enter' && submit()} + placeholder={get_text('location_input_placeholder')}/> + loc_change(null) || submit(null)}> + × + +
) diff --git a/src/index.js b/src/index.js index 30ffe135..6ec19c42 100644 --- a/src/index.js +++ b/src/index.js @@ -69,11 +69,8 @@ window.socket = async function (public_key, config) { } } - let options_required = false let error = null - if (!config.mode) { - options_required = true - } else if (MODES.indexOf(config.mode) === -1) { + if (config.mode && MODES.indexOf(config.mode) === -1) { error = `invalid mode "${config.mode}", options are: ${MODES.join(', ')}` config.mode = 'grid' } @@ -97,8 +94,6 @@ window.socket = async function (public_key, config) { // use history mode with enquiry so it doesn't add the hash if (config.mode === 'enquiry') { config.router_mode = 'history' - } else { - options_required = true } } else if (ROUTER_MODES.indexOf(config.router_mode) === -1) { error = `invalid router mode "${config.router_mode}", options are: ${ROUTER_MODES.join(', ')}` @@ -119,14 +114,6 @@ window.socket = async function (public_key, config) { delete config.labels_exclude } - if (config.subject_filter === undefined) { - config.subject_filter = true - } - - if (config.location_input === undefined) { - config.location_input = true - } - if (!config.event_callback) { config.event_callback = () => null } @@ -137,7 +124,6 @@ window.socket = async function (public_key, config) { return } - config.pagination = config.pagination || 100 config.messages = config.messages || {} for (let k of Object.keys(STRINGS)) { if (!config.messages[k]) { @@ -147,20 +133,31 @@ window.socket = async function (public_key, config) { config.random_id = Math.random().toString(36).substring(2, 10) config.grecaptcha_key = process.env.REACT_APP_GRECAPTCHA_KEY - if (options_required) { - let company_options - try { - company_options = await get_company_options(public_key, config) - } catch(e) { - error = e.toString() - company_options = { - display_mode: 'grid', - router_mode: 'hash', - } + let company_options + try { + company_options = await get_company_options(public_key, config) + } catch(e) { + error = e.toString() + // these are the default values + company_options = { + display_mode: 'grid', + pagination: 100, + router_mode: 'hash', + show_hours_reviewed: true, + show_labels: true, + show_location_search: true, + show_stars: true, + show_subject_filter: true, + sort_on: 'name', + } + } + + company_options.mode = company_options.display_mode + console.debug('company options:', company_options) + for (let [k, v] of Object.entries(company_options)) { + if (config[k] === undefined) { + config[k] = v } - console.debug('company options:', company_options) - config.mode = config.mode || company_options.display_mode - config.router_mode = config.router_mode || company_options.router_mode } console.debug('using config:', config) diff --git a/src/styles/contractor-filters.scss b/src/styles/contractor-filters.scss index 86c265be..edca44a7 100644 --- a/src/styles/contractor-filters.scss +++ b/src/styles/contractor-filters.scss @@ -14,41 +14,25 @@ background: darken($brand-colour, 10%); } -//.cross { -// position: absolute; -// top: 1px; -// right: 1px; -// margin: 5px 4px; -// padding: 4px 4px; -// cursor: pointer; -// z-index: 10; -// svg.tcs-svg { -// transition: all .4s ease; -// opacity: 0.5; -// width: 18px; -// height: 18px; -// } -// &:hover { -// svg.tcs-svg { -// opacity: 1; -// } -// } -//} - .tcs-filters-container { display: flex; } .tcs-contractor-filter { width: 50%; - padding: 0 2px; box-sizing: border-box; } +.Select { + margin-left: 4px; +} + .tcs-location-filter { + margin-right: 4px; border-radius: 4px; border: 1px solid #ccc; height: 36px; + padding: 0 2px; } .tcs-location-input { From 34ee60a602345fd54bd0e6bf5e00792d7f3eefa8 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Tue, 13 Feb 2018 17:58:52 +0000 Subject: [PATCH 4/4] fix tests --- src/tests/App.test.js | 6 ++--- src/tests/index.test.js | 2 +- src/tests/utils.js | 50 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/src/tests/App.test.js b/src/tests/App.test.js index 2602fb52..2979b1e5 100644 --- a/src/tests/App.test.js +++ b/src/tests/App.test.js @@ -1,7 +1,7 @@ import React from 'react' import {BrowserRouter as Router} from 'react-router-dom' import App from '../components/App' -import { xhr_setup, tick } from './utils' +import { xhr_setup, tick, STRINGS } from './utils' beforeEach(() => { xhr_setup() @@ -12,7 +12,7 @@ it('shows tutors', async () => { router_mode: 'history', api_root: 'https://socket.tutorcruncher.com', mode: 'grid', - messages: {no_tutors_found: 'xx'}, + messages: STRINGS, event_callback: () => null, } const wrapper = enz.mount( u}/>) @@ -33,7 +33,7 @@ it('with con filter', async () => { router_mode: 'history', api_root: 'https://socket.tutorcruncher.com', mode: 'grid', - messages: {no_tutors_found: 'xx'}, + messages: STRINGS, contractor_filter: { label: ['foobar'], label_exclude: ['spam'], diff --git a/src/tests/index.test.js b/src/tests/index.test.js index a16b2515..37567f4b 100644 --- a/src/tests/index.test.js +++ b/src/tests/index.test.js @@ -13,10 +13,10 @@ it('renders grid', async () => { router_mode: 'hash', mode: 'grid', }) + // console.log(pretty_html(div.innerHTML)) expect(r.goto).toBeTruthy() expect(r.config.contractor_filter).toEqual({}) expect(div.querySelectorAll('.tcs-contractors').length).toBe(1) - // console.log(pretty_html(div.innerHTML)) }) diff --git a/src/tests/utils.js b/src/tests/utils.js index a38e4feb..4503351e 100644 --- a/src/tests/utils.js +++ b/src/tests/utils.js @@ -9,8 +9,8 @@ const RESPONSES = { }), 'GET:https://socket.tutorcruncher.com/good/contractors': () => ({ status: 200, - content: JSON.stringify( - [ + content: JSON.stringify({ + 'results': [ { 'id': 213386, 'url': 'https://socket.tutorcruncher.com/9c79f14df986a1ec693c/contractors/213386', @@ -35,7 +35,24 @@ const RESPONSES = { 'photo': 'https://socket.tutorcruncher.com/media/9c79f14df986a1ec693c/213385.thumb.jpg', 'distance': null } - ]) + ] + }) + }), + 'GET:https://socket.tutorcruncher.com/good/options': () => ({ + status: 200, + content: JSON.stringify({ + name: 'Demo Branch', + name_display: 'first_name_initial', + show_stars: true, + display_mode: 'list', + router_mode: 'hash', + show_hours_reviewed: true, + show_labels: true, + show_location_search: true, + show_subject_filter: true, + sort_on: 'name', + pagination: 10, + }) }) } @@ -85,3 +102,30 @@ export function xhr_setup () { global.xhr_calls = [] global.XMLHttpRequest = MockXMLHttpRequest } + +// taken directly from index.js +export const STRINGS = { + skills_label: 'Skills', + contractor_enquiry: 'Please enter your details below to enquire about tutoring with {contractor_name}.', + enquiry: 'Please enter your details below and we will get in touch with you shortly.', + contractor_enquiry_button: 'Contact {contractor_name}', + contractor_details_button: 'Show Profile', + submit_enquiry: 'Submit Enquiry', + enquiry_submitted_thanks: 'Enquiry submitted, thank you.', + enquiry_modal_submitted_thanks: 'Enquiry submitted, thank you.\n\nYou can now close this window.', + enquiry_button: 'Get in touch', + enquiry_title: 'Enquiry', + grecaptcha_missing: 'This captcha is required', + required: ' (Required)', + subject_filter_placeholder: 'Select a subject...', + subject_filter_summary_single: '{subject}: showing 1 result', + subject_filter_summary_plural: '{subject}: showing {count} results', + location_input_placeholder: 'Enter your address or zip/postal code...', + view_profile: 'View Profile', + review_hours: '({hours} hours)', + previous: 'Previous', + next: 'Next', + no_tutors_found: 'No more tutors found', + no_tutors_found_loc: 'No more tutors found near this location', + distance_away: '{distance}km away', +}