|
| 1 | +import React, { Component } from 'react' |
| 2 | +import {IfElse, Markdown, DetailGrid, Detail} from '../shared/Tools' |
| 3 | +import {Tick} from '../shared/Svgs' |
| 4 | +import Modal from '../shared/Modal' |
| 5 | + |
| 6 | +const LS_KEY = '_tcs_user_data_' |
| 7 | + |
| 8 | +class AptModal extends Component { |
| 9 | + constructor (props) { |
| 10 | + super(props) |
| 11 | + this.login = this.login.bind(this) |
| 12 | + this.process_message = this.process_message.bind(this) |
| 13 | + this.update_session = this.update_session.bind(this) |
| 14 | + this.check_client = this.check_client.bind(this) |
| 15 | + this.signout = this.signout.bind(this) |
| 16 | + this.state = { |
| 17 | + signature: null, |
| 18 | + sso_data: null, |
| 19 | + display_data: null, |
| 20 | + appointment_attendees: null, |
| 21 | + } |
| 22 | + this.timeout_id = null |
| 23 | + window.addEventListener('message', this.process_message, false) |
| 24 | + this.update_session(window.sessionStorage[LS_KEY]) |
| 25 | + } |
| 26 | + |
| 27 | + login () { |
| 28 | + const url = `${this.props.config.auth_url}?site=${encodeURIComponent(window.location.href)}` |
| 29 | + window.open(url, 'Auth', 'width=1000,height=700,left=100,top=100,scrollbars,toolbar=0,resizable') |
| 30 | + } |
| 31 | + |
| 32 | + process_message (event) { |
| 33 | + const success = this.update_session(event.data) |
| 34 | + if (success) { |
| 35 | + event.source.close() |
| 36 | + window.sessionStorage[LS_KEY] = event.data |
| 37 | + } |
| 38 | + } |
| 39 | + |
| 40 | + update_session (raw_data) { |
| 41 | + let data |
| 42 | + try { |
| 43 | + data = JSON.parse(raw_data) |
| 44 | + } catch (e) { |
| 45 | + return false |
| 46 | + } |
| 47 | + data.display_data = JSON.parse(data.sso_data) |
| 48 | + this.setState(data) |
| 49 | + this.check_client(data) |
| 50 | + return true |
| 51 | + } |
| 52 | + |
| 53 | + async check_client (data) { |
| 54 | + data = data || this.state |
| 55 | + const args = {signature: data.signature, sso_data: data.sso_data} |
| 56 | + try { |
| 57 | + const r = await this.props.root.requests.get('check-client', args, {set_app_state: false}) |
| 58 | + this.setState({appointment_attendees: r.appointment_attendees[this.apt_id] || [], booking_allowed: true}) |
| 59 | + } catch (e) { |
| 60 | + if (e.xhr.status === 401) { |
| 61 | + this.signout() |
| 62 | + } else { |
| 63 | + this.props.root.setState({error: e.msg}) |
| 64 | + } |
| 65 | + } |
| 66 | + } |
| 67 | + |
| 68 | + signout () { |
| 69 | + this.setState({ |
| 70 | + signature: null, |
| 71 | + sso_data: null, |
| 72 | + display_data: null, |
| 73 | + appointment_attendees: null, |
| 74 | + }) |
| 75 | + window.sessionStorage.removeItem(LS_KEY) |
| 76 | + } |
| 77 | + |
| 78 | + render () { |
| 79 | + if (!this.props.got_data) { |
| 80 | + return ( |
| 81 | + <Modal history={this.props.history} title=''> |
| 82 | + <p>Loading...</p> |
| 83 | + </Modal> |
| 84 | + ) |
| 85 | + } |
| 86 | + const apt = this.state.apt |
| 87 | + if (!apt) { |
| 88 | + return ( |
| 89 | + <Modal history={this.props.history} title="Appointment not Found"> |
| 90 | + <p>No Appointment found with id {this.props.id}.</p> |
| 91 | + </Modal> |
| 92 | + ) |
| 93 | + } |
| 94 | + const title = ( |
| 95 | + <span> |
| 96 | + <span className="tcs-circle" style={{background: apt.service_colour}}/> |
| 97 | + {apt.topic} |
| 98 | + </span> |
| 99 | + ) |
| 100 | + const students = this.get_students() |
| 101 | + const spaces_available = apt.attendees_max - apt.attendees_count - this.state.extra_attendees |
| 102 | + const booking_allowed = this.state.booking_allowed && spaces_available > 0 |
| 103 | + return ( |
| 104 | + <Modal history={this.props.history} title={title} last_url={this.props.last_url} flex={false}> |
| 105 | + <div className="tcs-modal-flex"> |
| 106 | + <div className="tcs-extra"> |
| 107 | + <div className="tcs-price"> |
| 108 | + {this.props.config.format_money(apt.price)} |
| 109 | + <div className="tcs-label">Price</div> |
| 110 | + </div> |
| 111 | + </div> |
| 112 | + <div className="tcs-content"> |
| 113 | + <DetailGrid> |
| 114 | + <Detail label="Job"> |
| 115 | + {apt.service_name} |
| 116 | + </Detail> |
| 117 | + {apt.attendees_max && <Detail label="Spaces Available">{spaces_available}</Detail>} |
| 118 | + <Detail label="Start" className="tcs-new-line">{this.props.config.format_datetime(apt.start)}</Detail> |
| 119 | + <Detail label="Finish">{this.props.config.format_datetime(apt.finish)}</Detail> |
| 120 | + {apt.location && <Detail label="Location">{apt.location}</Detail>} |
| 121 | + </DetailGrid> |
| 122 | + </div> |
| 123 | + </div> |
| 124 | + |
| 125 | + <div> |
| 126 | + {apt.service_extra_attributes.map((attr, i) => ( |
| 127 | + <div key={i} className="tcs-attr"> |
| 128 | + <h3 className="tcs-attr-title">{attr.name}</h3> |
| 129 | + <IfElse v={attr.type === 'text_short' || attr.type === 'text_extended'}> |
| 130 | + <Markdown content={attr.value}/> |
| 131 | + {/*else*/} |
| 132 | + <p>{attr.value}</p> |
| 133 | + </IfElse> |
| 134 | + </div> |
| 135 | + ))} |
| 136 | + <div className="tcs-book"> |
| 137 | + <IfElse v={this.state.display_data}> |
| 138 | + <div> |
| 139 | + {students && ( |
| 140 | + <div className="tcs-book-existing"> |
| 141 | + <div>Add your existing Students to the lesson</div> |
| 142 | + {students.map(({id, name, already_on_apt}) => ( |
| 143 | + <div key={id} className="tcs-book-item"> |
| 144 | + <div className="tcs-existing-name"> |
| 145 | + {name} |
| 146 | + </div> |
| 147 | + {already_on_apt ? ( |
| 148 | + <div className="tcs-already-added"> |
| 149 | + Added <Tick/> |
| 150 | + </div> |
| 151 | + ) : ( |
| 152 | + <button className="tcs-button tcs-add-button" |
| 153 | + onClick={() => this.book(id)} |
| 154 | + disabled={!booking_allowed || already_on_apt}> |
| 155 | + {this.props.root.get_text('add_to_lesson')} |
| 156 | + </button> |
| 157 | + )} |
| 158 | + </div> |
| 159 | + ))} |
| 160 | + </div> |
| 161 | + )} |
| 162 | + <div className="tcs-book-new"> |
| 163 | + <div>Add a new Student to the lesson</div> |
| 164 | + <div className="tcs-book-item"> |
| 165 | + <input type="text" |
| 166 | + className="tcs-default-input" |
| 167 | + placeholder="Student Name" |
| 168 | + required={true} |
| 169 | + maxLength={255} |
| 170 | + value={this.state.new_student || ''} |
| 171 | + onChange={e => this.setState({new_student: e.target.value})}/> |
| 172 | + <button className="tcs-button tcs-add-button" |
| 173 | + onClick={() => this.book(null)} |
| 174 | + disabled={!booking_allowed || !this.state.new_student}> |
| 175 | + {this.props.root.get_text('add_to_lesson')} |
| 176 | + </button> |
| 177 | + </div> |
| 178 | + </div> |
| 179 | + </div> |
| 180 | + {/*else*/} |
| 181 | + <button className="tcs-button tcs-signin" onClick={this.login}> |
| 182 | + {this.props.root.get_text('book_appointment_button')} |
| 183 | + </button> |
| 184 | + </IfElse> |
| 185 | + </div> |
| 186 | + |
| 187 | + {this.state.display_data && |
| 188 | + <div className="tcs-session-name"> |
| 189 | + {this.state.display_data.nm} |
| 190 | + <div className="tcs-signout" onClick={this.signout}> |
| 191 | + Not you? sign out |
| 192 | + </div> |
| 193 | + </div> |
| 194 | + } |
| 195 | + </div> |
| 196 | + </Modal> |
| 197 | + ) |
| 198 | + } |
| 199 | +} |
| 200 | + |
| 201 | +export default AptModal |
0 commit comments