Added abaility for admins to add registrations
This commit is contained in:
parent
68146a446b
commit
481c186e08
@ -52,6 +52,7 @@
|
||||
request.open('POST', url, true);
|
||||
cells = editRow.getElementsByClassName('cell-editor');
|
||||
data.append('key', row.getAttribute('data-key'));
|
||||
data.append('button', 'update');
|
||||
var changed = false;
|
||||
for (var i = 0; i < cells.length; i++) {
|
||||
if (cells[i].value !== cells[i].getAttribute('data-value')) {
|
||||
@ -133,4 +134,32 @@
|
||||
|
||||
searchControl.addEventListener('keyup', filterTable);
|
||||
searchControl.addEventListener('search', filterTable);
|
||||
|
||||
forEachElement('[data-expands]', function(button) {
|
||||
button.addEventListener('click', function(event) {
|
||||
var element = document.getElementById(event.target.getAttribute('data-expands'));
|
||||
document.body.classList.add('expanded-element');
|
||||
element.classList.add('expanded');
|
||||
});
|
||||
});
|
||||
forEachElement('[data-contracts]', function(button) {
|
||||
button.addEventListener('click', function(event) {
|
||||
var element = document.getElementById(event.target.getAttribute('data-contracts'));
|
||||
document.body.classList.remove('expanded-element');
|
||||
element.classList.remove('expanded');
|
||||
});
|
||||
});
|
||||
forEachElement('[data-opens-modal]', function(button) {
|
||||
button.addEventListener('click', function(event) {
|
||||
var element = document.getElementById(event.target.getAttribute('data-opens-modal'));
|
||||
document.body.classList.add('modal-open');
|
||||
element.classList.add('open');
|
||||
});
|
||||
});
|
||||
forEachElement('[data-closes-modal]', function(element) {
|
||||
element.addEventListener('click', function(event) {
|
||||
document.getElementById(event.target.getAttribute('data-closes-modal')).classList.remove('open');
|
||||
document.body.classList.remove('modal-open');
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
@ -1,3 +1,5 @@
|
||||
$bug-list: ("search-element-appearance": ());
|
||||
|
||||
@import "bumbleberry";
|
||||
@import "settings";
|
||||
|
||||
@ -186,102 +188,114 @@ table, .table {
|
||||
|
||||
tr[data-key] {
|
||||
cursor: cell;
|
||||
|
||||
&:hover {
|
||||
|
||||
&.editable:hover {
|
||||
background-color: lighten($colour-2, 33%);
|
||||
}
|
||||
|
||||
&.editable {
|
||||
+ .editor {
|
||||
display: none;
|
||||
background-color: lighten($colour-1, 50%);
|
||||
|
||||
td {
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
background: inherit;
|
||||
cursor: default;
|
||||
opacity: 0.5;
|
||||
+ .editor {
|
||||
display: none;
|
||||
background-color: lighten($colour-1, 50%);
|
||||
|
||||
&.has-editor {
|
||||
opacity: 1;
|
||||
td {
|
||||
opacity: 0.5;
|
||||
|
||||
@include after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 0.25em;
|
||||
background-color: rgba($black, 0.125);
|
||||
}
|
||||
}
|
||||
&.has-editor {
|
||||
opacity: 1;
|
||||
|
||||
.cell-editor {
|
||||
top: 0;
|
||||
@include after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: inherit;
|
||||
font: inherit;
|
||||
margin: inherit;
|
||||
background: inherit;
|
||||
border: 0;
|
||||
min-height: 0;
|
||||
width: 100% !important;
|
||||
border-radius: 0;
|
||||
line-height: inherit;
|
||||
overflow: hidden;
|
||||
box-shadow: none;
|
||||
text-align: inherit;
|
||||
|
||||
&[type=number]::-webkit-inner-spin-button,
|
||||
&[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
select.cell-editor {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-ms-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.date .cell-editor {
|
||||
text-align-last: right;
|
||||
height: 0.25em;
|
||||
background-color: rgba($black, 0.125);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.editing {
|
||||
display: none;
|
||||
|
||||
+ .editor {
|
||||
display: table-row;
|
||||
|
||||
.cell-editor {
|
||||
position: absolute;
|
||||
.cell-editor {
|
||||
&[type=number]::-webkit-inner-spin-button,
|
||||
&[type=number]::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
select.cell-editor {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
-ms-appearance: none;
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.date .cell-editor {
|
||||
text-align-last: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*td[name] {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
+ .editor, &.always-edit {
|
||||
td {
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
background: inherit;
|
||||
cursor: default;
|
||||
|
||||
.cell-editor {
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: inherit;
|
||||
font: inherit;
|
||||
margin: inherit;
|
||||
background: inherit;
|
||||
border: 0;
|
||||
min-height: 0;
|
||||
width: 100% !important;
|
||||
border-radius: 0;
|
||||
line-height: inherit;
|
||||
overflow: hidden;
|
||||
box-shadow: none;
|
||||
text-align: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.always-edit td .cell-editor {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&.editing {
|
||||
display: none;
|
||||
|
||||
+ .editor {
|
||||
display: table-row;
|
||||
|
||||
.cell-editor {
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.always-editing {
|
||||
tr {
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($colour-2, 25%);
|
||||
}
|
||||
|
||||
input, textarea, select {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-editing="1"] {
|
||||
}*/
|
||||
.cell-editor {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
td.text {
|
||||
height: 5em;
|
||||
}
|
||||
}
|
||||
|
||||
tr.editable, tr.editor {
|
||||
@ -320,7 +334,97 @@ table, .table {
|
||||
|
||||
.table-scroller {
|
||||
overflow: auto;
|
||||
background-color: $white;
|
||||
background-color: #F8F8F8;
|
||||
@include _(box-shadow, inset 0 0 10em 0 rgba(0,0,0,0.125));
|
||||
|
||||
table {
|
||||
background-color: $white;
|
||||
margin: 0 0 8.5em;
|
||||
}
|
||||
|
||||
body.expanded-element .expanded & {
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.goes-fullscreen {
|
||||
[data-contracts] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body.expanded-element {
|
||||
overflow: hidden;
|
||||
|
||||
.goes-fullscreen.expanded {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
background-color: $white;
|
||||
overflow: auto;
|
||||
padding: 0 1em;
|
||||
|
||||
[data-expands] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[data-contracts] {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#main .columns .modal-edit {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1001;
|
||||
margin: 0;
|
||||
background-color: rgba($black, 0.5);
|
||||
|
||||
&.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal-edit-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.modal-edit-content {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
max-width: 40em;
|
||||
margin: auto;
|
||||
overflow: auto;
|
||||
z-index: 1002;
|
||||
background-color: #F8F8F8;
|
||||
}
|
||||
|
||||
table {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
@ -1111,6 +1215,10 @@ fieldset {
|
||||
button, .button {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
|
||||
+ button, + .button {
|
||||
margin-left: 0.75em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -508,6 +508,7 @@ class ConferencesController < ApplicationController
|
||||
column_types: {
|
||||
name: :bold,
|
||||
date: :datetime,
|
||||
email: :email,
|
||||
companion_email: :email,
|
||||
arrival: [:date, :day],
|
||||
departure: [:date, :day],
|
||||
@ -532,7 +533,7 @@ class ConferencesController < ApplicationController
|
||||
bike: 'forms.labels.generic.bike',
|
||||
food: 'forms.labels.generic.food',
|
||||
companion: 'articles.conference_registration.terms.companion',
|
||||
companion_email: 'forms.labels.generic.email',
|
||||
companion_email: 'articles.conference_registration.terms.companion_email',
|
||||
allergies: 'forms.labels.generic.allergies',
|
||||
registration_fees_paid: 'articles.conference_registration.headings.fees_paid',
|
||||
other: 'articles.conference_registration.headings.other',
|
||||
@ -645,7 +646,7 @@ class ConferencesController < ApplicationController
|
||||
preferred_language: I18n.backend.enabled_locales.map { |l| [
|
||||
(view_context.language_name l), l
|
||||
] },
|
||||
is_attending: yes_no,
|
||||
is_attending: [yes_no.first],
|
||||
can_provide_housing: yes_no,
|
||||
first_day: view_context.conference_days_options_list(:before),
|
||||
last_day: view_context.conference_days_options_list(:after)
|
||||
@ -782,59 +783,80 @@ class ConferencesController < ApplicationController
|
||||
|
||||
case params[:admin_step]
|
||||
when 'stats'
|
||||
registration = ConferenceRegistration.where(
|
||||
id: params[:key].to_i,
|
||||
conference_id: @this_conference.id
|
||||
).limit(1).first
|
||||
user_changed = false
|
||||
params.each do | key, value |
|
||||
case key.to_sym
|
||||
when :city
|
||||
registration.city = value.present? ? view_context.location(Geocoder.search(value, language: @this_conference.locale).first, @this_conference.locale) : nil
|
||||
when :housing, :bike, :food, :allergies, :other
|
||||
registration.send("#{key.to_s}=", value)
|
||||
when :registration_fees_paid
|
||||
registration.registration_fees_paid = value.to_i
|
||||
when :can_provide_housing
|
||||
registration.send("#{key.to_s}=", value.present?)
|
||||
when :arrival, :departure
|
||||
registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil)
|
||||
when :companion_email
|
||||
registration.housing_data ||= {}
|
||||
registration.housing_data['companions'] = [value]
|
||||
when :preferred_language
|
||||
registration.user.locale = value
|
||||
user_changed = true
|
||||
when :is_attending
|
||||
registration.is_attending = value.present? ? 'y' : 'n'
|
||||
when :address, :phone, :first_day, :last_day, :notes
|
||||
registration.housing_data ||= {}
|
||||
registration.housing_data[key.to_s] = value
|
||||
if params[:button] == 'save' || params[:button] == 'update'
|
||||
if params[:button] == 'save'
|
||||
return do_404 unless params[:email].present? && params[:name].present?
|
||||
|
||||
user = User.find_by_email(params[:email]) || User.create(email: params[:email])
|
||||
user.firstname = params[:name]
|
||||
user.save!
|
||||
registration = ConferenceRegistration.new(
|
||||
conference: @this_conference,
|
||||
user_id: user.id,
|
||||
steps_completed: []
|
||||
)
|
||||
else
|
||||
if key.start_with?('language_')
|
||||
l = key.split('_').last
|
||||
if User.AVAILABLE_LANGUAGES.include? l.to_sym
|
||||
registration.user.languages ||= []
|
||||
if value.present?
|
||||
registration.user.languages |= [l]
|
||||
else
|
||||
registration.user.languages -= [l]
|
||||
end
|
||||
user_changed = true
|
||||
end
|
||||
elsif ConferenceRegistration.all_spaces.include? key.to_sym
|
||||
registration = ConferenceRegistration.where(
|
||||
id: params[:key].to_i,
|
||||
conference_id: @this_conference.id
|
||||
).limit(1).first
|
||||
end
|
||||
|
||||
user_changed = false
|
||||
params.each do | key, value |
|
||||
case key.to_sym
|
||||
when :city
|
||||
registration.city = value.present? ? view_context.location(Geocoder.search(value, language: @this_conference.locale).first, @this_conference.locale) : nil
|
||||
when :housing, :bike, :food, :allergies, :other
|
||||
registration.send("#{key.to_s}=", value)
|
||||
when :registration_fees_paid
|
||||
registration.registration_fees_paid = value.to_i
|
||||
when :can_provide_housing
|
||||
registration.send("#{key.to_s}=", value.present?)
|
||||
when :arrival, :departure
|
||||
registration.send("#{key.to_s}=", value.present? ? Date.parse(value) : nil)
|
||||
when :companion_email
|
||||
registration.housing_data ||= {}
|
||||
registration.housing_data['space'] ||= {}
|
||||
registration.housing_data['space'][key.to_s] = value
|
||||
registration.housing_data['companions'] = [value]
|
||||
when :preferred_language
|
||||
registration.user.locale = value
|
||||
user_changed = true
|
||||
when :is_attending
|
||||
registration.is_attending = value.present? ? 'y' : 'n'
|
||||
when :address, :phone, :first_day, :last_day, :notes
|
||||
registration.housing_data ||= {}
|
||||
registration.housing_data[key.to_s] = value
|
||||
else
|
||||
if key.start_with?('language_')
|
||||
l = key.split('_').last
|
||||
if User.AVAILABLE_LANGUAGES.include? l.to_sym
|
||||
registration.user.languages ||= []
|
||||
if value.present?
|
||||
registration.user.languages |= [l]
|
||||
else
|
||||
registration.user.languages -= [l]
|
||||
end
|
||||
user_changed = true
|
||||
end
|
||||
elsif ConferenceRegistration.all_spaces.include? key.to_sym
|
||||
registration.housing_data ||= {}
|
||||
registration.housing_data['space'] ||= {}
|
||||
registration.housing_data['space'][key.to_s] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
registration.user.save! if user_changed
|
||||
registration.save!
|
||||
|
||||
if params[:button] == 'save'
|
||||
return redirect_to register_step_path(@this_conference.slug, :administration)
|
||||
end
|
||||
|
||||
get_stats(true, params[:key].to_i)
|
||||
options = view_context.registrations_table_options
|
||||
options[:html] = true
|
||||
return render html: view_context.excel_rows(@excel_data, {}, options)
|
||||
end
|
||||
registration.user.save! if user_changed
|
||||
registration.save!
|
||||
get_stats(true, params[:key].to_i)
|
||||
options = view_context.registrations_table_options
|
||||
options[:html] = true
|
||||
return render html: view_context.excel_rows(@excel_data, {}, options)
|
||||
when 'edit'
|
||||
case params[:button]
|
||||
when 'save'
|
||||
|
@ -1538,6 +1538,27 @@ module ApplicationHelper
|
||||
end
|
||||
end
|
||||
|
||||
def html_edit_table(excel_data, options = {})
|
||||
attributes = { class: options[:class], id: options[:id] }
|
||||
attributes[:data] = { 'update-url' => options[:editable] } if options[:editable].present?
|
||||
|
||||
content_tag(:table, attributes) do
|
||||
(content_tag(:tbody) do
|
||||
rows = ''
|
||||
excel_data[:columns].each do |column|
|
||||
if (excel_data[:column_types] || {})[column] != :table && ((options[:column_names] || []).include? column)
|
||||
rows += content_tag(:tr, { class: 'always-edit', data: { key: '' } }) do
|
||||
attributes = { class: [excel_data[:column_types][column]], data: { 'column-id' => column } }
|
||||
columns = content_tag(:th, excel_data[:keys][column].present? ? _(excel_data[:keys][column]) : '') +
|
||||
edit_column(nil, column, nil, attributes, excel_data, options)
|
||||
end
|
||||
end
|
||||
end
|
||||
rows.html_safe
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def html_table(excel_data, options = {})
|
||||
options[:html] = true
|
||||
attributes = { class: options[:class], id: options[:id] }
|
||||
@ -1672,38 +1693,46 @@ module ApplicationHelper
|
||||
end
|
||||
|
||||
if (options[:column_names] || []).include? column
|
||||
attributes[:class] << 'has-editor'
|
||||
raw_value = row[:raw_values][column] || value
|
||||
|
||||
if options[:html] && row[:html_values].present? && row[:html_values][column].present?
|
||||
value = row[:html_values][column]
|
||||
end
|
||||
|
||||
editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } }
|
||||
|
||||
# create the control but add the original value to set the width and height
|
||||
editor_value = content_tag(:div, value, class: 'value')
|
||||
if (options[:column_options] || {})[column].present?
|
||||
value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe
|
||||
elsif data[:column_types][column] == :text
|
||||
editor_attributes[:name] = column
|
||||
value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe
|
||||
else
|
||||
editor_attributes[:name] = column
|
||||
editor_attributes[:value] = raw_value
|
||||
type = data[:column_types][column] || :unknown
|
||||
editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text
|
||||
value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe
|
||||
end
|
||||
columns += edit_column(row, column, value, attributes, data, options)
|
||||
else
|
||||
columns += content_tag(:td, value, attributes)
|
||||
end
|
||||
|
||||
columns += content_tag(:td, value, attributes)
|
||||
end
|
||||
end
|
||||
|
||||
pad_columns(columns, padding)
|
||||
end
|
||||
|
||||
def edit_column(row, column, value, attributes, data, options)
|
||||
attributes[:class] << 'has-editor'
|
||||
raw_value = row.present? ? (row[:raw_values][column] || value) : nil
|
||||
|
||||
if row.present? && options[:html] && row[:html_values].present? && row[:html_values][column].present?
|
||||
value = row[:html_values][column]
|
||||
end
|
||||
|
||||
editor_attributes = { class: 'cell-editor', data: { value: raw_value.to_s } }
|
||||
|
||||
# create the control but add the original value to set the width and height
|
||||
editor_value = content_tag(:div, value, class: 'value')
|
||||
if (options[:column_options] || {})[column].present?
|
||||
value = (editor_value.html_safe + select_tag(column, options_for_select([['', '']] + options[:column_options][column], raw_value), editor_attributes)).html_safe
|
||||
elsif data[:column_types][column] == :text
|
||||
editor_attributes[:name] = column
|
||||
value = (editor_value.html_safe + content_tag(:textarea, raw_value, editor_attributes)).html_safe
|
||||
else
|
||||
editor_attributes[:name] = column
|
||||
editor_attributes[:value] = raw_value
|
||||
editor_attributes[:required] = :required if (options[:required_columns] || []).include? column
|
||||
type = data[:column_types][column] || :unknown
|
||||
editor_attributes[:type] = { money: :number, number: :number, email: :email }[type] || :text
|
||||
value = (editor_value.html_safe + content_tag(:input, nil, editor_attributes)).html_safe
|
||||
end
|
||||
|
||||
return content_tag(:td, value, attributes)
|
||||
end
|
||||
|
||||
def excel_sub_tables(row, data, padding = {}, options = {})
|
||||
rows = ''
|
||||
|
||||
@ -1747,6 +1776,15 @@ module ApplicationHelper
|
||||
rows.html_safe
|
||||
end
|
||||
|
||||
def registrations_edit_table_options
|
||||
options = registrations_table_options
|
||||
options[:id] = 'create-table'
|
||||
options[:class] << 'always-editing'
|
||||
options[:column_names] += [:name, :email]
|
||||
options[:required_columns] = [:name, :email]
|
||||
return options
|
||||
end
|
||||
|
||||
def registrations_table_options
|
||||
{
|
||||
id: 'search-table',
|
||||
|
@ -19,7 +19,19 @@
|
||||
.actions
|
||||
= link_to (_'links.download.Excel'), administration_step_path(@this_conference.slug, :stats, :format => :xlsx), class: [:button, :download]
|
||||
= link_to (_'links.download.Organizations_Excel'), administration_step_path(@this_conference.slug, :organizations, :format => :xlsx), class: [:button, :download, :subdued]
|
||||
%h4 Registrations
|
||||
= searchfield :search, nil, big: true
|
||||
.table-scroller
|
||||
= html_table @excel_data, registrations_table_options
|
||||
%h4=_'articles.admin.stats.headings.Registrations'
|
||||
.goes-fullscreen#registrations-table
|
||||
.flex-column
|
||||
= searchfield :search, nil, big: true, stretch: true
|
||||
%a.button{data: { expands: 'registrations-table' }}='expand'
|
||||
%a.button.delete{data: { contracts: 'registrations-table' }}='close'
|
||||
%a.button.modify{data: { 'opens-modal': 'new-registration' }}='+'
|
||||
.table-scroller
|
||||
= html_table @excel_data, registrations_table_options
|
||||
= form_tag administration_update_path(@this_conference.slug, :stats), id: 'new-registration', class: 'modal-edit' do
|
||||
.modal-edit-overlay{data: { 'closes-modal': 'new-registration' }}
|
||||
.modal-edit-content
|
||||
=html_edit_table @excel_data, registrations_edit_table_options
|
||||
.actions
|
||||
%a.button.subdued{data: { 'closes-modal': 'new-registration' }}='Cancel'
|
||||
= button_tag :save, value: :save, class: :modify
|
||||
|
@ -882,7 +882,8 @@ en:
|
||||
vegan: Vegans
|
||||
registrations: Number of registrations
|
||||
completed_registrations: Number of registrations
|
||||
incomplete_registrations: Incomplete registrations
|
||||
completed_registrations: Number of registrations
|
||||
Registrations: Registrations
|
||||
meals:
|
||||
description: On this page you can schedule the meals that you will be serving.
|
||||
no_locations_warning: Before you can add meals, you must first add locations.
|
||||
@ -1116,6 +1117,7 @@ en:
|
||||
preregistered: Preregistered
|
||||
registered: Registered
|
||||
companion: Companion
|
||||
companion_email: Companion Email
|
||||
Preferred_Languages: Preferred Language
|
||||
is_attending: Attending?
|
||||
about_bikebike:
|
||||
|
Loading…
x
Reference in New Issue
Block a user