Page MenuHomeIn-Portal Phabricator

modern-store
No OneTemporary

File Metadata

Created
Sun, Apr 20, 5:17 PM

modern-store

Index: branches/1.0.x/checkout.tpl
===================================================================
--- branches/1.0.x/checkout.tpl (revision 14805)
+++ branches/1.0.x/checkout.tpl (revision 14806)
@@ -1,246 +1,255 @@
<!--##
<NAME>One Step Checkout</NAME>
<DESC></DESC>
<SECTION>Pages||Checkout</SECTION>
##-->
<inp2:m_DefineElement name="page_title">One Step Checkout</inp2:m_DefineElement>
<!--## MAIN CONTENT ##-->
<inp2:m_DefineElement name="content">
<ul class="steps">
<li class="step1"><a href="<inp2:m_Link template='cart'/>"><i></i>Shopping Cart</a></li>
<li class="step2 active"><a href="#"><i></i>One-step Checkout</a></li>
<li class="step3"><span><i></i>Receipt</span></li>
</ul>
<div class="clear"></div>
<div class="cart-detail">
<h1>One-Step Checkout</h1>
<form method="post" action="<inp2:m_FormAction/>" id="checkout-form" class="form checkout-form">
<script type="text/javascript">
FormManager.resetFields('ord');
</script>
<inp2:ord_ShowDefaultAddress type="billing"/>
<inp2:ord_ShowDefaultAddress type="shipping"/>
<div class="halfcol">
<fieldset class="firstline">
<inp2:m_ifnot check="m_LoggedIn">
<div class="login-alt">
Continue as Guest or
<div class="login">
<a href="" id="login-alt">Log In</a>
</div>
</div>
</inp2:m_ifnot>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingEmail" title="lu_fld_YourEmail"/>
</fieldset>
<fieldset>
<div class="fieldset">
Billing Address
</div>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingTo" title="lu_fld_FullName"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingCompany" title="lu_fld_Company"/>
<inp2:m_RenderElement name="inp_edit_address" prefix="ord" field="BillingAddress1" field2="BillingAddress2" title="lu_fld_Address"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingCity" title="lu_fld_City"/>
<inp2:m_RenderElement name="inp_edit_options" prefix="ord" field="BillingCountry" title="lu_fld_Country" has_empty="1" empty_label="lu_opt_SelectCountry"/>
<!-- TODO: preload states by country -->
<inp2:m_RenderElement name="inp_edit_options" prefix="ord" field="BillingState" title="lu_fld_State" has_empty="1" row_class="alt1"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingZip" title="lu_fld_Zip" row_class="alt2"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="BillingPhone" title="lu_fld_Phone"/>
</fieldset>
<fieldset class="creditcard">
<div class="fieldset">
Payment Information
</div>
<div class="creditcards">
<span><img src="<inp2:m_TemplatesBase/>img/creditcards.jpg" width="245" height="39" alt="" /></span>
<span class="paypal"><img src="<inp2:m_TemplatesBase/>img/paypal.jpg" width="98" height="45" alt="" /></span>
</div>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="PaymentAccount" title="lu_fld_CreditCardNumber"/>
- <inp2:m_RenderElement design="form_row" prefix="ord" field="PaymentCCExpDate" title="lu_fld_ExpirationDate" row_class="alt1 expdate">
- <select class="field" name="<inp2:ord_InputName field='PaymentCCExpMonth'/>">
+ <inp2:m_RenderElement design="form_row" prefix="ord" field="PaymentCCExpDate" title="lu_fld_ExpirationDate" row_class="alt1 expdate" field_type="cc_expiration">
+ <select class="field" name="<inp2:ord_InputName field='PaymentCCExpMonth'/>" id="<inp2:ord_InputName field='PaymentCCExpMonth'/>">
<inp2:ord_PredefinedOptions field="PaymentCCExpMonth" render_as="inp_option_item" selected="selected"/>
</select> /
- <select class="field" name="<inp2:ord_InputName field='PaymentCCExpYear'/>">
+ <select class="field" name="<inp2:ord_InputName field='PaymentCCExpYear'/>" id="<inp2:ord_InputName field='PaymentCCExpYear'/>">
<option value=''></option>
<inp2:ord_PrintYearOptions field="PaymentCCExpYear" render_as="inp_option_item" selected="selected"/>
</select>
</inp2:m_RenderElement>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="PaymentCVV2" title="lu_fld_SecurityCode" row_class="alt2"/>
</fieldset>
</div>
<div class="halfcol halfcol-last">
<inp2:m_ifnot check="m_LoggedIn">
<div class="badge-optional"></div>
<fieldset class="firstline" id="create-account-password">
<p>
<label><input type="checkbox" id="create_account" name="create_account" value="1"<inp2:m_if check="m_Get" name="create_account"> checked</inp2:m_if>/> Create account for easy purchases and order tracking</label>
</p>
<inp2:m_RenderElement name="inp_edit_password" prefix="ord" field="UserPassword" title="lu_fld_Password" verify_title="lu_fld_VerifyPassword"/>
</fieldset>
</inp2:m_ifnot>
<fieldset>
<div class="fieldset">
<label>
<input type="checkbox" id="same_as_billing" name="same_as_billing" value="1"<inp2:m_if check="m_Get" name="same_as_billing"> checked</inp2:m_if>/> Ship To Address is the same as Billing
</label>
</div>
<div id="shipping-address"<inp2:m_if check="m_Get" name="same_as_billing"> style="display: none;"</inp2:m_if>>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="ShippingTo" title="lu_fld_FullName"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="ShippingCompany" title="lu_fld_Company"/>
<inp2:m_RenderElement name="inp_edit_address" prefix="ord" field="ShippingAddress1" field2="ShippingAddress2" title="lu_fld_Address"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="ShippingCity" title="lu_fld_City"/>
<inp2:m_RenderElement name="inp_edit_options" prefix="ord" field="ShippingCountry" title="lu_fld_Country" has_empty="1" empty_label="lu_opt_SelectCountry"/>
<!-- TODO: preload states by country -->
<inp2:m_RenderElement name="inp_edit_options" prefix="ord" field="ShippingState" title="lu_fld_State" has_empty="1" row_class="alt1"/>
<inp2:m_RenderElement name="inp_edit_box" prefix="ord" field="ShippingZip" title="lu_fld_Zip" row_class="alt2"/>
</div>
<p>
<label for="ship">How would you like the items shipped? <span class="star">*</span></label>
<select id="ship" class="field">
<option value="" selected="selected">FedEx Ground (Next Day) $14.00</option>
<option value="">USA</option>
<option value="">Russia</option>
</select>
</p>
<div class="safe-shop" id="shipping-address-placeholder"<inp2:m_ifnot check="m_Get" name="same_as_billing"> style="display: none;"</inp2:m_ifnot>>
<p class="title">100% Safe & Secure Shopping</p>
<ul class="list">
<li>All Transactions are Encrypted</li>
<li>Your Information is Protected</li>
<li>No Hidden Charges</li>
<li>Satisfaction Guaranteed</li>
</ul>
</div>
</fieldset>
<fieldset>
<div class="fieldset">
Your Order Summary (18 Items)
<a href="#" class="link">View Order Details</a>
</div>
<dl class="total">
<dt>Subtotal</dt>
<dd>$1,759.99</dd>
<dt class="discount">Discount:</dt>
<dd class="price2">- $10.00</dd>
<dt>Shipping</dt>
<dd class="price3">$100.00</dd>
<dt>Tax</dt>
<dd class="price3">$100.00</dd>
<dt>Order Total</dt>
<dd class="price4">$1,959.99</dd>
</dl>
<div class="clear"></div>
<div class="terms">
<label><input type="checkbox" id="terms" name="terms" value="yes" checked="true" />&nbsp;I Agree to Terms and Conditions</label>
</div>
<p class="buttons">
<a href="#" class="place-button"><span>Place Order</span></a>
</p>
</fieldset>
</div>
<div class="clear"></div>
+ <inp2:m_RenderElement name="inp_edit_hidden" prefix="ord" field="PaymentType" db="db"/>
+
<input type="hidden" name="check_billing_address" id="check_billing_address" value="true"/>
<input type="hidden" name="check_shipping_address" id="check_shipping_address" value="true"/>
<input type="hidden" name="check_credit_card" id="check_credit_card" value="true"/>
</form>
</div>
<script type="text/javascript">
$(document).ready(
function () {
$('#same_as_billing').click(
function ($e) {
var $checked = $(this).is(':checked');
$('#shipping-address').toggle(!$checked);
$('#shipping-address-placeholder').toggle($checked);
}
);
$('#create_account').click(
function ($e) {
$('.password-block').toggle( $(this).is(':checked') );
}
);
FormManager.registerForm(
{
prefix: 'ord',
save_event: 'OnCombinedPlaceOrder',
id: '<inp2:ord_Field name="OrderId"/>',
form_id: 'checkout-form'
}
);
$('.place-button').click(
function ($e) {
FormManager.submitForm('ord');
return false;
}
);
$('#checkout-form').submit(
function ($e) {
FormManager.submitForm('ord');
return false;
}
);
$('#login-alt').click(
function ($e) {
var $login_link = $('#login');
if ( !$login_link.parents('.login:first').hasClass('plashka-sel') ) {
$login_link.click();
}
return false;
}
);
$('body').bind(
'OnAfterLogin',
function ($e, $params) {
+ // 1. remove login button
$('.login-alt').remove();
+
+ // 2. remove user password controls
$('#create-account-password, .badge-optional').remove();
+ FormManager.unregisterField('ord', 'UserPassword');
+ FormManager.unregisterField('ord', 'VerifyUserPassword');
+ // 3. put user e-mail into the form
FormManager.setFieldStatus('ord', 'BillingEmail');
FormManager.getField('ord', 'BillingEmail').value = $params['user_email'];
}
)
}
);
$('.password-block').toggle( $('#create_account').is(':checked') );
</script>
</inp2:m_DefineElement>
<!--## /MAIN CONTENT ##-->
<!--## DESIGN TEMPLATE ##-->
+<inp2:m_Set check_credit_card="true"/>
<inp2:m_include template="designs/default_design.des" pass_params="1"/>
<!--## /DESIGN TEMPLATE ##-->
\ No newline at end of file
Index: branches/1.0.x/inc/js/form_manager.js
===================================================================
--- branches/1.0.x/inc/js/form_manager.js (revision 14805)
+++ branches/1.0.x/inc/js/form_manager.js (revision 14806)
@@ -1,515 +1,533 @@
function FormManager() { }
FormManager.init = function ($settings) {
$.ajaxSetup( {cache: false} );
this.url = '';
this.fieldMask = '#PREFIX#[#ID#][#FIELD_NAME#]';
- this.noErrorsHTML = ''
+ this.noErrorsHTML = '';
this.checkTimeout = 1000;
this.pendingChecks = {};
this.fields = {};
this.fieldTypes = {};
this.forms = {};
this.fieldWatermarks = {};
this.xhrRequests = [];
$.extend(this, $settings);
}
FormManager.resetFields = function ($prefix) {
this.fields[$prefix] = [];
}
FormManager.registerField = function ($prefix, $field, $watermark, $field_type) {
// fields are registered before form -> store them in separate array
if (!this.fields[$prefix]) {
this.fields[$prefix] = [];
}
if ($watermark === undefined) {
$watermark = '';
}
this.fieldWatermarks[$prefix + '_' + $field] = $watermark;
this.fields[$prefix].push($field);
if ($field_type !== undefined) {
this.fieldTypes[$prefix + '_' + $field] = $field_type;
}
}
+FormManager.unregisterField = function ($prefix, $field) {
+ var $field_index = array_search($field, this.fields[$prefix]);
+
+ this.fields[$prefix].splice($field_index, 1);
+
+ delete this.fieldWatermarks[$prefix + '_' + $field];
+ delete this.fieldTypes[$prefix + '_' + $field];
+}
+
+
FormManager.getFieldMask = function ($prefix) {
return this.fieldMask.replace('#PREFIX#', $prefix).replace('#ID#', this.form_param($prefix, 'id'));
}
FormManager.getField = function ($prefix, $field, $prepend, $append) {
if ($prepend === undefined) {
$prepend = '';
}
if ($append === undefined) {
$append = '';
}
var $control_id = this.getFieldMask($prefix).replace('#FIELD_NAME#', $field);
return document.getElementById($prepend + $control_id + $append);
}
FormManager.getBlurFields = function ($prefix, $field) {
var $field_mask = this.getFieldMask($prefix);
switch ( this.fieldTypes[$prefix + '_' + $field] ) {
case 'swf_upload':
return this.getField($prefix, $field, undefined, '[tmp_names]');
break;
case 'date':
$field += '_date';
break;
case 'radio':
return $("input[name='" + jq($field_mask.replace('#FIELD_NAME#', $field)) + "']");
break;
case 'checkbox':
return get_control($field_mask, $field, undefined, '_cb');
break;
case 'checkboxes':
return $("input[id^='" + jq($field_mask.replace('#FIELD_NAME#', $field)) + "_']");
break;
+
+ case 'cc_expiration':
+ return $('select', this.getCell($prefix, $field, 'field'));
+ break;
}
return this.getField($prefix, $field);
}
FormManager.registerForm = function ($settings) {
var $defaults = {
url: false, // url for form submission
prefix: '', // unit prefix, used in the form
enabled: true, // form submit enabled
enabledTimer: null, // timer that performs form-resubmit countdown
save_event: '', // event to use for form data processing
id: 0, // id of item being add/edited on a form
form_id: '', // form id to work with
before_close: '', // before window close callback
validation_failure: '', // on validate failure callback
immediate_validation: true // perfom validation on blur
}
this.forms[$settings.prefix] = {};
$.extend(this.forms[$settings.prefix], $defaults, $settings);
// when form is registred, then all it's fields should also be registred
if ( !this.form_param($settings.prefix, 'immediate_validation') ) {
return ;
}
var $me = this;
$( this.fields[$settings.prefix] ).each(
function () {
$( $me.getBlurFields($settings.prefix, this) ).blur(
function ($e) {
$me.checkField(this);
}
);
}
);
$('input[title], select[title], textarea[title]', '#' + this.forms[$settings.prefix].form_id).qtip(
{
style: {
classes: 'ui-tooltip-light ui-tooltip-shadow'
},
position: {
my: 'left center',
at: 'right center'
},
show: {
event: 'focus'
}
}
);
$('img.help-icon', '#' + this.forms[$settings.prefix].form_id ).qtip(
{
style: {
classes: 'ui-tooltip-light ui-tooltip-shadow'
},
position: {
my: 'bottom left',
at: 'top center'
}
}
);
}
FormManager.getURL = function ($prefix, $template, $event, $params) {
var $url = this.form_param($prefix, 'url');
if (!$url) {
$url = this.url;
}
if ($template === undefined) {
$template = '';
}
$url = $url.replace('#TEMPLATE#', $template);
if ($event !== undefined) {
$url += ($url.indexOf('?') == -1 ? '?' : '&') + 'events[' + $prefix + ']=' + $event;
}
if ( typeof($params) == 'object' ) {
for (key in $params) {
$url += ($url.indexOf('?') == -1 ? '?' : '&') + key + '=' + $params[key];
}
}
return $url;
}
FormManager.processResponse = function ($prefix, $data, $add_params) {
// enable form back
// alert('enabling for for [' + $prefix + '] in processResponse');
if ( $add_params !== undefined ) {
$add_params.response = $data;
}
else {
$add_params = {response: $data};
}
this.clearErrors($prefix);
if ($data.status == 'OK') {
var $next_template = this.getNextTemplate($prefix, $data);
if ( $next_template || $data.do_refresh ) {
var $before_close = this.getFormParamOverride($prefix, 'before_close', $add_params);
if ( $.isFunction($before_close) ) {
$before_close.call(this, $data, $add_params);
}
if ( $next_template ) {
// load another template instead of current form
$('#TB_ajaxContent').html('').load( this.getURL($prefix, $next_template, undefined, $data.params) );
}
else if ( $data.do_refresh ) {
// refresh whole page
window.location.href = window.location.href;
}
}
else {
// close form without refreshing the page
this.closeForm($prefix, $data, $add_params);
}
}
else {
// set new errors
for ($field in $data.field_errors) {
this.setFieldStatus($prefix, $field, $data.field_errors[$field]);
}
var $validation_failure = this.form_param($prefix, 'validation_failure');
if ( $.isFunction($validation_failure) ) {
$validation_failure.call(this, $data, $add_params);
}
}
this.enableForm($prefix, true);
// var $me = this;
// setTimeout(function () { $me.enableForm($prefix, true); }, 1000);
}
/**
* Clear errors from all from fields
*
* @param $prefix
*/
FormManager.clearErrors = function ($prefix) {
var $fields = this.fields[$prefix];
if (typeof($fields) == 'undefined') {
$fields = [];
}
for (var $i = 0; $i < $fields.length; $i++) {
this.setFieldStatus($prefix, $fields[$i]);
}
}
FormManager.getNextTemplate = function ($prefix, $responce) {
if ( $responce.next_template && $responce.next_template != '' ) {
return $responce.next_template;
}
else if ( this.form_param($prefix, 'next_template') ) {
return this.form_param($prefix, 'next_template');
}
return false;
}
FormManager.getCell = function ($prefix, $field_name, $cell_type) {
$field_name = $field_name.replace(/_(date|time)$/, '');
return this.getField($prefix, $field_name, undefined, '_' + $cell_type + '_cell');
}
FormManager.setFieldStatus = function ($prefix, $field_name, $error_msg) {
var field_cell = this.getCell($prefix, $field_name, 'field');
var status_cell = this.getCell($prefix, $field_name, 'status');
if (!field_cell) {
alert('Error field "' + $field_name + '" missing.');
return ;
}
if ($error_msg === undefined || !$error_msg) {
// show OK
$(field_cell).parents('p:first').removeClass('error'); //.addClass('ok');
$(status_cell)/*.removeClass('field-error')*/.html(this.noErrorsHTML);
var $fields = this.fields[$prefix];
for (var $i = 0; $i < $fields.length; $i++) {
if ( this.fieldHasError($prefix, $fields[$i]) ) {
return ;
}
}
}
else {
// show error message
$(field_cell).parents('p:first')/*.removeClass('ok')*/.addClass('error');
$(status_cell)/*.addClass('field-error')*/.html($error_msg);
}
}
FormManager.fieldHasError = function ($prefix, $field) {
var status_cell = this.getField($prefix, $field, undefined, '_status_cell');
return $.trim( $(status_cell).html() ) != $.trim( this.noErrorsHTML );
}
FormManager.checkField = function ($input, $delayed) {
if ( !$input.id.match(/^(.*?)\[.*?\]\[(.*?)\].*?$/) ) {
return ;
}
var $prefix = RegExp.$1;
var $field = RegExp.$2.replace(/(_date|_time)$/, '');
+ if ( $field.match(/(.*)(Month|Year)$/) && this.fieldTypes[$prefix + '_' + RegExp.$1 + 'Date'] == 'cc_expiration' ) {
+ $field = RegExp.$1 + 'Date';
+ }
+
if ( this.pendingChecks[$field] ) {
clearTimeout( this.pendingChecks[$field] );
delete this.pendingChecks[$field];
}
var $me = this;
this.pendingChecks[$field] = setTimeout(
function () {
$me.validateField($prefix, $field, $input)
},
($delayed === true ? this.checkTimeout : 0)
);
}
FormManager.validateField = function ($prefix, $field, $input) {
var $me = this;
var form = document.getElementById( this.form_param($prefix, 'form_id') );
var $request = $.post(
this.getURL($prefix, undefined, 'OnValidateField') + '&field=' + encodeURIComponent($field) + '&' + $input.name + '=' + encodeURIComponent($input.value),
$(form).serialize(),
function ($data) {
$data = eval('(' + $data + ')');
$me.setFieldStatus($prefix, $field, $data.status == 'OK' ? undefined : $data.status);
$("input[type='submit']", form).attr('disabled', !$.isArray($data.other_errors) );
}
);
this.xhrRequests.push($request);
};
FormManager.form_param = function ($prefix, $param, $value) {
if ($value === undefined) {
return this.forms[$prefix][$param];
}
this.forms[$prefix][$param] = $value;
}
FormManager.getFormParamOverride = function ($prefix, $param, $overrides) {
if ( $overrides[$param] !== undefined ) {
return $overrides[$param];
}
return this.form_param($prefix, $param);
}
/* === related to form opening/closing/submitting === */
FormManager.openForm = function ($prefix, $template, $width, $height, $source_form, $params) {
var $url = this.getURL($prefix, $template, undefined, $params);
$url += ($url.indexOf('?') == -1 ? '?' : '&') + 'width=' + $width + '&height=' + $height + '&modal=true';
var $tb_settings = {url: $url};
if ($source_form !== undefined) {
$tb_settings.postParams = $($source_form).serialize();
}
TB.show($tb_settings);
}
FormManager.validateAll = function ($prefix, $status) {
var $fields = this.fields[$prefix];
for (var $i = 0; $i < $fields.length; $i++) {
this.setFieldStatus($prefix, $fields[$i], $status);
}
}
FormManager.closeForm = function ($prefix, $data, $add_params) {
if ( $data === undefined ) {
$data = {};
}
if ( $add_params === undefined ) {
$add_params = {};
}
var $before_close = this.getFormParamOverride($prefix, 'before_close', $add_params);
this.cancelXHRRequests();
this.validateAll($prefix);
if ( $.isFunction($before_close) ) {
var $result = $before_close.call(this, $data, $add_params);
if ($result === false) {
return;
}
}
TB.remove();
}
FormManager._getFormFields = function ($prefix) {
var $fields = this.fields[$prefix];
if (typeof($fields) == 'undefined') {
$fields = [];
}
// remove watermakrs from input fields
for (var $i = 0; $i < $fields.length; $i++) {
var $control = this.getField($prefix, $fields[$i]);
var $watermark = this.fieldWatermarks[ $prefix + '_' + $fields[$i] ];
if ($control && $watermark !== undefined && $control.value == $watermark) {
$control.value = '';
}
}
var form = document.getElementById( this.form_param($prefix, 'form_id') );
var $form_fields = $(form).serialize();
// restore watermarks in input fields
for (var $i = 0; $i < $fields.length; $i++) {
var $control = this.getField($prefix, $fields[$i]);
var $watermark = this.fieldWatermarks[$prefix + '_' + $fields[$i]];
if ($control && $watermark !== undefined && !$control.value) {
$control.value = $watermark;
}
}
return $form_fields;
}
FormManager.enableForm = function ($prefix, $enabled) {
if ($enabled === undefined) {
$enabled = true;
}
if ($enabled) {
clearTimeout( this.form_param($prefix, 'enabledTimer') );
this.form_param($prefix, 'enabledTimer', null);
}
else {
var $me = this;
// set timer for 10 seconds to enable form back (just in case if ajax responce fails)
var $timer = setTimeout(
function () {
// alert('enabling for for [' + $prefix + '] in setTimeout');
$me.enableForm($prefix, true);
}
, 10000
);
this.form_param($prefix, 'enabledTimer', $timer);
}
this.form_param($prefix, 'enabled', $enabled);
}
FormManager.cancelXHRRequests = function () {
while ( this.xhrRequests.length > 0 ) {
this.xhrRequests.shift().abort();
}
}
FormManager.submitForm = function ($prefix, $add_params) {
if ( !this.form_param($prefix, 'enabled') ) {
return ;
}
// disable form
this.enableForm($prefix, false);
var $me = this;
this.cancelXHRRequests();
$.post(
this.getURL( $prefix, '', this.form_param($prefix, 'save_event') ),
this._getFormFields($prefix),
function ($data) {
var $redirect = TB.parseRedirect($data);
if ( $redirect !== false ) {
window.location.href = $redirect;
return ;
}
$me.processResponse($prefix, eval('(' + $data + ')'), $add_params);
}
)
}
FormManager.beforeClose = function () {
this.cancelXHRRequests();
for (var $prefix in this.forms) {
var $before_close = this.form_param($prefix, 'before_close');
if ( $.isFunction($before_close) ) {
$before_close.call(this, {}, {});
}
}
}
\ No newline at end of file

Event Timeline