Jump to content

Developer centric form processor


Mike Rockett

Recommended Posts

Edit: The title of this post has been renamed to use the word "processor" instead of "builder" - this is not a form-building module.

I'm currently putting together a simple developer-centric form processor for one of my projects, and have decided that I'd like to release it as a module as soon as it's stable enough.

The idea is to separate it from the backend, and use per-form configuration files and templates instead. I could well implement a backend solution for this, but my preference is for it to be developer-centric. For me, this makes it easier to use and, of course, develop.

Here's how it currently works: developer specifies forms and their particulars in a JSON file stored in the module's directory. The file includes dictations such as the name of the form, the fields it uses (along with their sanitization/validation rules), template information, and a set of emails to send when the form is being processed.

An example of such configuration is:

{
    "contact": {
        "name": "Contact Form",
        "fields": {
            "name": {
                "sanitize": "text",
                "rules": {
                    "required": "Please enter your name."
                }
            },
            "email": {
                "sanitize": "email",
                "rules": {
                    "required": "We need to know your email address so we can get back to you.",
                    "email": "That doesn't look like a valid email address."
                }
            },
            "company": {
                "sanitize": "text",
                "rules": {
                    "min(4)": "That's a tad short for a company name."
                }
            },
            "contact": {
                "sanitize": "text",
                "rules": {
                    "int": "Please enter only the digits of your phone number (no spaces or other punctuation)."
                }
            },
            "message": {
                "sanitize": "entities1|textarea",
                "textField": true,
                "rules": {
                    "required": "Please enter your enquiry/message.",
                    "min(250)": "Please enter at least {$0} (but no more than 2000) characters.",
                    "max(2000)": "You have reached the {$0} character limit. Please shorten your message."
                }
            }
        },
        "info": {
            "fromName": "The ABC Accounting Team",
            "tel": "(011) 100 1234",
            "altTel": "(011) 100 5678"
        },
        "emails": {
            "autoReply": {
                "template": "auto-reply",
                "to": "{input.name} <{input.email}>",
                "from": "ABC Accounting <noreply@abc.accounting>",
                "subject": "Enquiry/Message Submitted - Thanks!"
            }
        }
    }
}

As seen in the 'emails' key, templates can be defined for each email. These templates can be plain/HTML/both, and accept information regarding form input, the 'info' key, and a custom stylesheet, which is created as a file, but imported directly into the HTML version of the template.

The module will also come with a jQuery module to assist in processing the form. Frontend is up to the developer/designer.

Currently, the directory structure of the module is:

root
- forms.config.json
/ templates
  / form-name
    - template-name.html
    - template-name.css
    - template-name.txt

I'm now thinking that it would be better to change the structure to this:

root
/ form-name
  / templates
    - template-name.html
    - template-name.css
    - template-name.txt
  - config.json

That is: each form will have its own folder containing configuration and templates.

So I'm starting this thread to ask the following:

  1. Firstly, what do you think of this idea, and do you think you would make use of this module?
  2. Of the two structures above, which one would you prefer?
  3. Would you want the ability to make use of attachments in your emails (such as a logo)? (If I'm not mistaken, we'd then need to require WireMailSmtp or WireMailSwiftMailer...)

As a side note, it's worth mentioning that this module is really intended to be used on small- to medium-sized sites that require multiple forms where developers are not in a position to obtain Ryan's excellent FormBuilder.

Any input here is most welcome.

(And yes, as gathered by my signature, the module is called SimpleForms. If you have a name suggestion, please feel free...)

Edited by Mike Rockett
  • Like 15
Link to comment
Share on other sites

I like this idea!

In my former CMS there was a developer that works on all his addons with a basic form-addon that works with exact this methode (json for setup, twig .htt template files for the HTML Output) he called that forms "presets".

So answering your questions:

1.)idea is great for every frontend development that usecases are

     a)simple forms easy to create without ravish backend inputs for forms or rely on other modules

     b)complexer setups for frontend forms of different types that are easy and very fast to develop with the same schema

2.) keep every form data (json/html/..) separate so structure 2 has my vote - It keeps the filetree clean i think

3.) Would always go for that feature since many forms are have attachments today like applications, userimage upload, image uploads for special propose
     (on a older site i've just a little form for a village where people could send a foto from a damage or request on the inframstructure/lamps and so on)

4.) wiresmtp or swift should be a better choice than normal PHP mailer...

One question on my side.

How do you wanna handle the HTML templates and the processing of the inputfields? Could i setup a schema or preset for the HTML output of every inputfield (for example to get bootstrap or UI Kit output) or you catch the replacements of processing with some sort of template engine or simple PHP tags...?

Best regards mr-fan

  • Like 1
Link to comment
Share on other sites

I like this idea!

In my former CMS there was a developer that works on all his addons with a basic form-addon that works with exact this methode (json for setup, twig .htt template files for the HTML Output) he called that forms "presets".

So answering your questions:

1.)idea is great for every frontend development that usecases are

     a)simple forms easy to create without ravish backend inputs for forms or rely on other modules

     b)complexer setups for frontend forms of different types that are easy and very fast to develop with the same schema

2.) keep every form data (json/html/..) separate so structure 2 has my vote - It keeps the filetree clean i think

3.) Would always go for that feature since many forms are have attachments today like applications, userimage upload, image uploads for special propose

     (on a older site i've just a little form for a village where people could send a foto from a damage or request on the inframstructure/lamps and so on)

4.) wiresmtp or swift should be a better choice than normal PHP mailer...

One question on my side.

How do you wanna handle the HTML templates and the processing of the inputfields? Could i setup a schema or preset for the HTML output of every inputfield (for example to get bootstrap or UI Kit output) or you catch the replacements of processing with some sort of template engine or simple PHP tags...?

Best regards mr-fan

I'm glad you do, and thanks for the input!

I can already see the second directory structure being the preferred one - it makes much more sense. Will await other feedback on the front, nonetheless.

Regarding attachments, I was thinking more along the lines of template-driven attachments. So, for example, you may wish to include your logo in the auto-reply sent to the user - it would be better to use an attachment (file saved on server) as email clients do not automatically show linked/external images.

The module simply makes use of wireMail, and so all extensions will be supported. So, for attachment functionality, I'll be making the feature available, but the module will check to see if the required wireMail extensions are available. (I haven't actually checked if these modules support attachments.)

I do think it will be a good idea to allow user uploads as well. Will work on this over the weekend. (The module's core functionality works, so just adding and tweaking based on what is discussed in this thread.)

A great idea - I've been thinking about something similar for a while but just don't have the time myself so will be keeping an eye on this with interest :)

Great stuff - once all the adding and tweaking is done, I'll swing this over to GitHub for testing.

I'm guessing you both like the name SimpleForms?

Link to comment
Share on other sites

One question on my side.

How do you wanna handle the HTML templates and the processing of the inputfields? Could i setup a schema or preset for the HTML output of every inputfield (for example to get bootstrap or UI Kit output) or you catch the replacements of processing with some sort of template engine or simple PHP tags...?

Sorry, forgot to respond to this part.

I'm using a simple StringTemplate engine for the email templates. The objective is to easily allow the developer to specify variables, such as {info.company} and {input.name}. It's not a fully-featured template engine - I don't believe this is really required here. The goal with this module is to make simple forms quickly.

Here's an example of an email template:

{stylesheet}
<div class="body">
	<p><strong>Dear {input.name}</strong></p>
	<h3>Your message has been processed.</h3>
	<p>This communication serves to confirm that our website has processed your message. We should receive it shortly, and will get back to you as soon as we can (usually within two working days).</p>
	<p>If you need to get hold of us urgently, please call us on {info.tel} or {info.altTel}.</p>
	<p>For your reference, your details and message appear at the bottom of this email.</p>
	<p class="nM">Kind Regards,</p>
	<p>{info.fromName}</p>
	<div class="line"></div>
	<p class="notice">Please note that this email was sent to you from an unattended mailbox. Replies to this email will not be received.</p>
	<div class="box">
		<p><strong>Your Details and Message:</strong></p>
		<p class="nM">Email Address: {input.email}</p>
		<p class="nM">Contact Number: {input.contact}</p>
		<p>Company Name: {input.company}</p>
		<p>{input.message.html}</p>
	</div>
</div>

In terms of the front-end, I haven't gotten there as yet. I will be building a simple "middleman" jQuery plugin to handle the AJAX processing of the form. Essentially, the developer would need to design the form, and place the HTML for that form directly into their templates. I don't want the module to make any assumptions on how the form should be laid out. The only requirement would be for the developer/designer to insert the necessary tags in which errors will be displayed.

Off the top of my head, here's an example of how the front-end would work:

<form id="contactForm" data-simpleforms-form="contact"> <!-- auto-resolve to modules/simple-forms/contact -->
	<div class="inputGroup">
		<label for="name">Your name:</label>
		<input type="text" id="name" data-name="name">
		<div data-simpleforms-error="name"></div>
	</div>
	<div class="inputGroup">
		<label for="company">Company name:</label>
		<input type="text" id="company" data-name="company">
		<div data-simpleforms-error="company"></div>
	</div>	
	<!-- etc. -->
</form>

<script> $('[data-simple-forms-form]').simpleForms(); </script>
Link to comment
Share on other sites

QuickForms? SimpleForms works too. RockettForms sounds cool too :)

I'm actually less interested in the processing than I am of the very first part - JSON array converting to HTML output (which needs to be configurable of course for different form elements so you can use with different UI kits or file uploaders etc).

Link to comment
Share on other sites

My name suggestion would be: RockettForms

Hehe, nice one. Though, I'm not a fan of naming things after myself... Will stick with SimpleForms for now. Pete's suggestion is also quite nice - it does indeed portray the essence of the module's purpose.

I'm actually less interested in the processing than I am of the very first part - JSON array converting to HTML output (which needs to be configurable of course for different form elements so you can use with different UI kits or file uploaders etc).

The original intention of this module was to simply the process of building simple forms - such as contact and feedback forms. To me, adding on extra functionality like an HTML form builder seems a tad too much in terms of the intention. I feel it would be better to leave the front-end markup to the developer/designer.

However, and with that said, I'm willing to reconsider should there be a decent amount of demand for it. For one, I don't look at these things often enough, because I don't really have an interest in using UI kits. I prefer to use a good responsive grid framework, and design from scratch. I'm sure, though, that many developers/designers prefer UI kits, and so I would consider making this an optional extra.

Thoughts?

Link to comment
Share on other sites

Good luck on this but be prepared for a long term development :)

I actually have a form processor that I use in several PW projects. It gets form data from Nette forms and creates a page on each submission. It's kinda drop-in but fully developer oriented. Of course it surely has some flaws but it gets better on each project.

If I were you I wouldn't reinvent the wheel as there are many proved solutions out there. I see more potential in integrating a 3rd party form and leave the heavy lifting to others :)

Link to comment
Share on other sites

Good luck on this but be prepared for a long term development :)

I actually have a form processor that I use in several PW projects. It gets form data from Nette forms and creates a page on each submission. It's kinda drop-in but fully developer oriented. Of course it surely has some flaws but it gets better on each project.

If I were you I wouldn't reinvent the wheel as there are many proved solutions out there. I see more potential in integrating a 3rd party form and leave the heavy lifting to others :)

Thanks, tpr. My current plan is to, at the very least, get a base-functional module up soon. I have quite a bit of work coming up really soon (not dev-related), and so won't be able to put much time into it for a little while after the initial release. However, this will be a good time for user-feedback that I can look at when I continue development.

Whilst there are proven solutions out there, I haven't seen one as flexible as this (other than FormBuilder, of course). I have actually given some thought to Pete's suggestions, and will be going ahead with some kind of HTML builder for each form.

@everyone:

There are so many ways one could go about building each form as automagically as possible. So I'm going to put together some ideas and share them with you. As stated before, I'm going to make this available as an opt-in feature - you'd be able to choose between something like $forms->render('contact') and rendering the form manually.

I think that one of the best approaches would be to allow the developer to specify what kind of form-output is needed for each form. Choices would include 'bootstrap', 'foundation', and 'processwire' (using Inputfields). Based on that specification, the render() method would pick out the applicable HTML blueprints/builder-methods and render the form from there. Of course, and based on the rendering spec, each field would need to have certain properties defined - only properties that make a difference to each spec would be required.

Anyways, let me give this some more thought. In the meantime, I'm going to change the directory structure, and release a prototype for feedback purposes as soon as its ready.

  • Like 2
Link to comment
Share on other sites

Have just committed 0.1.0 to GitHub.

The bundle comes with a preset contact form definition, used for easy testing.

All that needs to be done after installation is the HTML form, which is below for copy-paste purposes:

<form data-simpleforms="contact" action="<?=$simpleForms->actionFor('contact')?>" method="POST">

    <?=$simpleForms->csrfToken()?>

    <div data-simpleforms-formerror></div>

    <div>
        <input type="text" name="name" id="name" placeholder="Your name (required)">
        <div data-simpleforms-fielderror="name"></div>
    </div>

    <div>
        <input type="email" name="email" id="email" placeholder="Your email address (required)">
        <div data-simpleforms-fielderror="email"></div>
    </div>

    <div>
        <input type="text" name="company" id="company" placeholder="Company name (optional)">
        <div data-simpleforms-fielderror="company"></div>
    </div>

    <div>
        <input type="text" name="contact" id="contact" placeholder="Contact number (optional)">
        <div data-simpleforms-fielderror="contact"></div>
    </div>

    <div>
        <textarea name="message" id="message" cols="30" rows="10"></textarea>
        <div data-simpleforms-fielderror="message"></div>
    </div>

    <div>
        <input type="submit" data-value="Submit Form">
    </div>
</form>

Of course, this form is not styled, and is very basic. This is just for testing purposes.

actionFor simply prints the URI to the form processor for this form.

csrfToken prints the hidden CSRF token.

The next step is to ensure jQuery is loaded on the page - I tested with 1.8.3 (bundled with ProcessWire) only, so not sure if any bugs will pop up in later versions.

Then, link up the front-end jQuery asset with either of the following:

<!-- Cache-busted (default); outputs /site/modules/SimpleForms/assets/jquery.simpleforms.min.js?<unix_time> -->
<script src="<?=$simpleForms->script();?>"></script>

<!-- Non-cache-busted; outputs /site/modules/SimpleForms/assets/jquery.simpleforms.min.js -->
<script src="<?=$simpleForms->script(false, true);?>"></script>

<!-- Non-cache-busted, non-minified; outputs /site/modules/SimpleForms/assets/jquery.simpleforms.js -->
<script src="<?=$simpleForms->script(false, false);?>"></script>

And lastly, prepare the form using any of the following methods (just above the closing body tag):

<!-- Just the contact form. -->
<script>$("[data-simpleforms=contact]").simpleForms();</script>

<!-- Or all the forms - all of these are the same, so best to use the last one. -->
<script>$("[data-simpleforms!=''][data-simpleforms]").simpleForms();</script>
<script>$(allSimpleForms()).simpleForms();</script>
<script>simpleForms();</script>

Form definitions have been moved to their own directories, as discussed previously. Have a look at the forms/contact directory, which contains the config and templates.

Have also included a @todo list in the module's main file so you can see what's in the pipeline. For easy reference, here's the current list:

/* @todo Auto-prepend existing stylesheet to HTML template - need not rely on template var {stylesheet}.
 * @todo Template attachments (inline data or attachment reference - perhaps we should rely on SwiftMailer for this?).
 * @todo Use reply link builder (for cases where emails may not be sent from sender - usually the case with SMTP).
 * @todo Translate all the things!
 * @todo Make module configurable - allow for default form-recipient and noreply/auto-response sender.
 * @todo Simple form builder, based on JSON specifications.
 * @todo Make AJAX optional, using standard form submission protocols.
 */

There are many configuration options still to come; I'd like to make this as configurable as possible, but without code-bloating the module.

Looking forward to input from testers - this is, of course, nowhere near perfect, but is a good base/blueprint from which to start.

Once we've reached feature-parity and the module is stable, I'll release it on a new thread, and put up some documentation.

  • Like 2
Link to comment
Share on other sites

Forgot to mention something here:
 
<div data-simpleforms-formerror></div>
 
The front-end plugin fills this block with the validation error message, as defined in config.json. The one used in this testing release is as follows:
 
<strong>Could not submit form</strong><br>{ucfirst:0} validation [error|errors:0] [was|were:0] found on the form, preventing it from being submitted. Please check the [error|errors:0] below (as indicated in the red [box|boxes:0]), correct the [field|fields:0] in question, and try again.
 
As you can see, I've implemented my own simple JS templater here - simply called plate. The method in the plugin passes the error count (in digits and words, parameters 0 and 1 respectively) to the string. plate is intended to be a simple templater that replaces {0}, {1}, {2}, etc., with the arguments passed to it. Additionally, it does word-pluralisation if a number is provided as the parameter, and is able to ucfirst() a parameter on demand.
 
In the case of the validation message:

  • {ucfirst:0} will be replaced with the error count in words
  • [word|words:0] will be replaced with either 'word' or 'words' based on the error count (in words). [word|words:1] would do the same thing.

The plugin prepares everything like this:

var errorCount = objectLength(errors);
var errorCountWords = toWords(errorCount);
var errorNotification = plate(responseData.error, errorCountWords, errorCount);

Essentially, this is just a helper to make things as easy as possible when you want to be specific and informative. You need not use params if you don't want to.
 
If you'd like to use plate in your own projects, please feel free to do so - no credits required. Here's the method:

/**
 * String template parser.
 * @return string
 */
var plate = function() {
    var input = arguments[0];
    var ucfirstMethod = 'ucfirst';

    // Loop through each argument, checking for replacements.
    for (var i = 0; i < arguments.length - 1; i++) {
        var formatter = new RegExp("\\{(" + ucfirstMethod + "\?" + i + "\\}", "gm");
        var pluralise = new RegExp("\\[([a-z]+)\\|([a-z]+):(" + i + ")\\]", "gmi");
        var argument = arguments[i + 1];

        // Detech ucfirst as toWords() always returns lowercase.
        if (input.match(formatter) && input.match(formatter)[0].indexOf(ucfirstMethod) >= 0) {
            argument = argument.charAt(0).toUpperCase() + argument.slice(1);
        }

        // Replace the input
        input = input.replace(formatter, argument);

        // Check for plurals/singulars
        if (input.match(pluralise)) {
            input = input.replace(pluralise, (argument === 1 || argument.toLowerCase() === 'one') ? "$1" : "$2");
        }
    }

    return input;
}

Validation is handled by Violin, a package by Alex Garrett. Have a look at the readme to see which validation rules are available to you.

Link to comment
Share on other sites

This looks nice. I'll be testing this shortly, but I do have a question. Just glancing through the module, why are you setting 500 errors, or 422, etc. rather than throwing errors?

I should probably be using a different error code for errors relating to the module itself. 422 seems to be the acceptable standard for invalid input - Laravel is using it. The errors are thrown in JSON format at present - this is an AJAX-driven module.

Link to comment
Share on other sites

Okay/ Was just curious. I'm here to learn as much as I can. Which at my age isn't easy. :)

Sure thing - we never stop learning. :-)

Just a sidenote for everyone regarding the 500 errors. When a server error is returned, the plugin only alerts the user to the effect that they are told tha something went wrong. The reason can be determined by examining the request in the inspector.

The plugin also has events for when the submit button is pressed (i.e. processing begins), when a response is received (i.e. processing finishes), and when a server error occurs. the simpleForms() method takes a configuration array, the defaults for which are:

{
    interimButtonText: 'Just a moment...',
    serverErrorAlert: 'Something went wrong on the server, and so this form could not be submitted. The form has been left as-is so that you may leave it open and try again in a few minutes.',
    scrollTime: 500,
    onSubmitStart: function() {},
    onSubmitEnd: function() {},
    onServerError: null,
}

The methods do not take any arguments at present, but will do in the future. This will be handy when you want to use your own callbacks to display errors, dim out a form, show a processing message, etc. As mentioned before, more configuration options will become available over time.

:-)

  • Like 1
Link to comment
Share on other sites

...This will be handy when you want to use your own callbacks to display errors, dim out a form, show a processing message, etc...

That is where my question came from.

Looking forward to trying this out soon. Nice work.

  • Like 1
Link to comment
Share on other sites

So I seem to have left out a crucial part for the jQuery plugin: the part when the form submission is successful. :o

Will get this done soon. For now, if the module returns a 200 (meaning the form was processed successfully), there will not be any change on the form itself. I'll be setting it to either remove the form and show a message or call a user event method.

Link to comment
Share on other sites

New Commit: Plugin fixes and improvements

Version Bump: 0.2.0

Fix: config pass to simpleForms()

Configuration was previously not passed when using the simpleForms() shorthand method.

Imp.: configuration structure (deep extension)

Configuration array has been modified, now making use of deep extension.The default configuration now looks like this:

{
    interimButtonText: 'Just a moment...',
    logErrors: true,
    processingClass: 'processing',
    scroll: {
        enabled: true,
        offset: 0,
        duration: 500,
        easing: 'swing',
    },
    serverErrorAlert: 'Something went wrong on the server, and so this form could not be submitted. The form has been left as-is so that you may leave it open and try again in a few minutes.',
    events: {

        // Supplementary methods
        submitStart: function(form) {},
        submitEnd: function(form) {},

        // Override methods
        success: null, // function(form, message)
        failure: null, // function(form, errors, message)
        serverError: null, // function(message)
    }
}

Imp.: callbacks (overrides and supplementaries)

All known callbacks have now been implemented. As seen above, submitStart and submitEnd are supplementary methods, which are called in addition to the defaults. success, failure, and serverError are override methods.

New: Add processingClass to form on submitStart

I had a case where I needed the form to be translucent when processing, and so the class has been added - you may style this as you wish. The name of the class may also be changed (see above config).

Fix: AJAX options (global: true)

AJAX event should adhere to global options.

Fix: Add AJAX 200 OK

200 OK statusCode now implemented. Either make use of the success event, or it will fall back to default, which replaces the form with the success message determined in config.json; plate format: <div data-simpleforms-success class="sfSuccessMessage">{0}</div>

Imp.: Scrolling options

Scrolling has been improved. Per the above configuration, you can now turn it on or off, and change duration, easing, and offset (pixels above the form).

Link to comment
Share on other sites

New Commit: Add formatter.js (plus multiple script support)

Version Bump: 0.3.0

This commit adds formatter.js support to the front-end plugin. You need only include the plugin and specify the applicable formats.

Add the script before the main script:

<script src="<?=$simpleForms->script('formatter');?>"></script>
<script src="<?=$simpleForms->script();?>"></script>

Specify the formats for each field:

simpleForms({
    inputFormats: {
        contact: "({{999}}) {{999}}-{{9999}}"
    }
});

Each input field is defined here by name (that is, the [name=""] of the field). You may either set a pattern (as above) or a configuration object (as below):

simpleForms({
    inputFormats: {
        contact: {
            pattern: "({{999}}) {{999}}-{{9999}}",
            persistent: true, // always show literal characters
        }
    }
});

To see how formatter.js works, check out the demos.

Link to comment
Share on other sites

New Commit: Add inputmask.js support

Version Bump: 0.4.0

This commit adds inputmask.js support, which can be used when you need more flexibility with your masks.

Due to the fact that there are now two plugins for masking, the configuration keys represent their names. Instead of inputFormats, you should now use either formatter or inputmask.

To use inputmask.js, simply call the script, and specify the applicable formats:

<script src="<?=$simpleForms->script('inputmask');?>"></script>
<script src="<?=$simpleForms->script();?>"></script>
simpleForms({
    inputmask: { 
        contact: "(999) 999-9999"
    }
});
// OR
simpleForms({
    inputmask: { 
        contact: {
             mask: "(999) 999-9999",
             placeholder: "\u2007", // figure space (quite handy)
        }
    }
});
Learn more about inputmask.js.
Link to comment
Share on other sites

Update regarding the HTML builder:

This is obviously something that people would be interested in, especially when it comes to getting forms up and running as quickly as possible. However, it should be noted that I don't intend on making a full suite to handle this due to the fact that every form is different, and every scenario cannot simply be assumed.

However, the builder will be able to output simple form layouts that do not require specific attributes to be set. At best, the JSON configuration for each form will accomodate classes/IDs for each input field, and will output the form in either Bootstrap, Foundation, or ProcessWire InputField format. If that doesn't suit you, then you should build the form yourself. As this is a developer-centric module, it is recommended that you design your own forms anyway. This gives you the fine grained control you may need - flexibility is key, here.

I'll continue to work on this for the next week or so, and will push major updates as they're completed. The next major feature will be the builder, or at least a very simple implementation of one.

With that said, the module is currently working as expected (form-processing), and already does more than I'd anticipated (input-masking), which puts a smile on my face anyway. I'll also be working on a full set of docs soon.

The todo list has been moved to the repo's readme file.

  • Like 2
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...