1
0
mirror of https://github.com/invoiceninja/invoiceninja.git synced 2024-11-10 05:02:36 +01:00

Merge pull request #9224 from beganovich/31-rff

Required form fields
This commit is contained in:
David Bomba 2024-02-04 11:21:34 +11:00 committed by GitHub
commit 6966c5cc61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 213 additions and 81 deletions

View File

@ -106,6 +106,12 @@ class PaymentController extends Controller
*/
public function process(Request $request)
{
$request->validate([
'contact_first_name' => ['required'],
'contact_last_name' => ['required'],
'contact_email' => ['required', 'email'],
]);
return (new InstantPayment($request))->run();
}

View File

@ -351,5 +351,12 @@ class ClientContact extends Authenticatable implements HasLocalePreference
return config('ninja.react_url')."/#/clients/{$this->client->hashed_id}";
}
public function showRff(): bool
{
if (\strlen($this->first_name) === 0 || \strlen($this->last_name) === 0 || \strlen($this->email) === 0) {
return true;
}
return false;
}
}

View File

@ -45,6 +45,15 @@ class InstantPayment
public function run()
{
nlog($this->request->all());
$cc = auth()->guard('contact')->user();
$cc->first_name = $this->request->contact_first_name;
$cc->last_name = $this->request->contact_last_name;
$cc->email = $this->request->contact_email;
$cc->save();
$is_credit_payment = false;
$tokens = [];

2
package-lock.json generated
View File

@ -1,5 +1,5 @@
{
"name": "invoiceninja",
"name": "@invoiceninja/invoiceninja",
"lockfileVersion": 2,
"requires": true,
"packages": {

View File

@ -1,9 +0,0 @@
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/class s{constructor(e,t){this.shouldDisplayTerms=e,this.shouldDisplaySignature=t,this.termsAccepted=!1,this.submitting=!1}handleMethodSelect(e){document.getElementById("company_gateway_id").value=e.dataset.companyGatewayId,document.getElementById("payment_method_id").value=e.dataset.gatewayTypeId,this.shouldDisplaySignature&&!this.shouldDisplayTerms&&(this.signaturePad&&this.signaturePad.isEmpty()&&alert("Please sign"),this.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",()=>{this.termsAccepted=!0,this.submitForm()})),!this.shouldDisplaySignature&&this.shouldDisplayTerms&&(this.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",()=>{document.querySelector('input[name="signature"').value=this.signaturePad.toDataURL(),this.submitForm()})),this.shouldDisplaySignature&&this.shouldDisplayTerms&&(this.displaySignature(),document.getElementById("signature-next-step").addEventListener("click",()=>{this.displayTerms(),document.getElementById("accept-terms-button").addEventListener("click",()=>{document.querySelector('input[name="signature"').value=this.signaturePad.toDataURL(),this.termsAccepted=!0,this.submitForm()})})),!this.shouldDisplaySignature&&!this.shouldDisplayTerms&&this.submitForm()}submitForm(){this.submitting=!0,document.getElementById("payment-form").submit()}displayTerms(){document.getElementById("displayTermsModal").removeAttribute("style")}displaySignature(){document.getElementById("signature-next-step").disabled=!0,document.getElementById("displaySignatureModal").removeAttribute("style");const t=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"});t.onEnd=function(){document.getElementById("signature-next-step").disabled=!1},this.signaturePad=t}handle(){document.querySelectorAll(".dropdown-gateway-button").forEach(e=>{e.addEventListener("click",()=>{this.submitting||this.handleMethodSelect(e)})})}}const i=document.querySelector('meta[name="require-invoice-signature"]').content,a=document.querySelector('meta[name="show-invoice-terms"]').content;new s(!!+i,!!+a).handle();

View File

@ -0,0 +1,9 @@
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2021. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://www.elastic.co/licensing/elastic-license
*/class s{constructor(t,e,a){this.shouldDisplayTerms=t,this.shouldDisplaySignature=e,this.shouldDisplayRff=a,this.submitting=!1,this.steps=new Map,this.shouldDisplayRff&&this.steps.set("rff",{element:document.getElementById("displayRequiredFieldsModal"),nextButton:document.getElementById("rff-next-step"),callback:()=>{const n={firstName:document.querySelector('input[name="rff_first_name"]'),lastName:document.querySelector('input[name="rff_last_name"]'),email:document.querySelector('input[name="rff_email"]')};n.firstName&&(document.querySelector('input[name="contact_first_name"]').value=n.firstName.value),n.lastName&&(document.querySelector('input[name="contact_last_name"]').value=n.lastName.value),n.email&&(document.querySelector('input[name="contact_email"]').value=n.email.value)}}),this.shouldDisplaySignature&&this.steps.set("signature",{element:document.getElementById("displaySignatureModal"),nextButton:document.getElementById("signature-next-step"),boot:()=>this.signaturePad=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"}),callback:()=>document.querySelector('input[name="signature"').value=this.signaturePad.toDataURL()}),this.shouldDisplayTerms&&this.steps.set("terms",{element:document.getElementById("displayTermsModal"),nextButton:document.getElementById("accept-terms-button")})}handleMethodSelect(t){if(document.getElementById("company_gateway_id").value=t.dataset.companyGatewayId,document.getElementById("payment_method_id").value=t.dataset.gatewayTypeId,this.steps.size===0)return this.submitForm();const e=this.steps.values().next().value;e.element.removeAttribute("style"),e.boot&&e.boot(),console.log(e),e.nextButton.addEventListener("click",()=>{e.element.setAttribute("style","display: none;"),this.steps=new Map(Array.from(this.steps.entries()).slice(1)),e.callback&&e.callback(),this.handleMethodSelect(t)})}submitForm(){this.submitting=!0,document.getElementById("payment-form").submit()}handle(){document.querySelectorAll(".dropdown-gateway-button").forEach(t=>{t.addEventListener("click",()=>{this.submitting||this.handleMethodSelect(t)})})}}const i=document.querySelector('meta[name="require-invoice-signature"]').content,o=document.querySelector('meta[name="show-invoice-terms"]').content,l=document.querySelector('meta[name="show-required-fields-form"]').content;new s(!!+o,!!+i,!!+l).handle();

View File

@ -23,7 +23,7 @@
"src": "resources/js/clients/invoices/action-selectors.js"
},
"resources/js/clients/invoices/payment.js": {
"file": "assets/payment-0ace5bfa.js",
"file": "assets/payment-1bdbd169.js",
"isEntry": true,
"src": "resources/js/clients/invoices/payment.js"
},

View File

@ -9,11 +9,60 @@
*/
class Payment {
constructor(displayTerms, displaySignature) {
constructor(displayTerms, displaySignature, displayRff) {
this.shouldDisplayTerms = displayTerms;
this.shouldDisplaySignature = displaySignature;
this.termsAccepted = false;
this.shouldDisplayRff = displayRff;
this.submitting = false;
this.steps = new Map()
if (this.shouldDisplayRff) {
this.steps.set("rff", {
element: document.getElementById('displayRequiredFieldsModal'),
nextButton: document.getElementById('rff-next-step'),
callback: () => {
const fields = {
firstName: document.querySelector('input[name="rff_first_name"]'),
lastName: document.querySelector('input[name="rff_last_name"]'),
email: document.querySelector('input[name="rff_email"]'),
}
if (fields.firstName) {
document.querySelector('input[name="contact_first_name"]').value = fields.firstName.value;
}
if (fields.lastName) {
document.querySelector('input[name="contact_last_name"]').value = fields.lastName.value;
}
if (fields.email) {
document.querySelector('input[name="contact_email"]').value = fields.email.value;
}
}
});
}
if (this.shouldDisplaySignature) {
this.steps.set("signature", {
element: document.getElementById('displaySignatureModal'),
nextButton: document.getElementById('signature-next-step'),
boot: () => this.signaturePad = new SignaturePad(
document.getElementById("signature-pad"),
{
penColor: "rgb(0, 0, 0)"
}
),
callback: () => document.querySelector('input[name="signature"').value = this.signaturePad.toDataURL(),
});
}
if (this.shouldDisplayTerms) {
this.steps.set("terms", {
element: document.getElementById('displayTermsModal'),
nextButton: document.getElementById('accept-terms-button'),
});
}
}
handleMethodSelect(element) {
@ -22,54 +71,32 @@ class Payment {
element.dataset.companyGatewayId;
document.getElementById("payment_method_id").value =
element.dataset.gatewayTypeId;
if (this.shouldDisplaySignature && !this.shouldDisplayTerms) {
if(this.signaturePad && this.signaturePad.isEmpty())
alert("Please sign");
this.displayTerms();
document
.getElementById("accept-terms-button")
.addEventListener("click", () => {
this.termsAccepted = true;
this.submitForm();
});
if (this.steps.size === 0) {
return this.submitForm();
}
if (!this.shouldDisplaySignature && this.shouldDisplayTerms) {
this.displaySignature();
const next = this.steps.values().next().value;
document
.getElementById("signature-next-step")
.addEventListener("click", () => {
document.querySelector('input[name="signature"').value = this.signaturePad.toDataURL();
this.submitForm();
});
next.element.removeAttribute("style");
if (next.boot) {
next.boot();
}
if (this.shouldDisplaySignature && this.shouldDisplayTerms) {
this.displaySignature();
console.log(next);
document
.getElementById("signature-next-step")
.addEventListener("click", () => {
this.displayTerms();
next.nextButton.addEventListener('click', () => {
next.element.setAttribute("style", "display: none;");
document
.getElementById("accept-terms-button")
.addEventListener("click", () => {
document.querySelector('input[name="signature"').value = this.signaturePad.toDataURL();
this.termsAccepted = true;
this.submitForm();
});
});
}
this.steps = new Map(Array.from(this.steps.entries()).slice(1));
if (!this.shouldDisplaySignature && !this.shouldDisplayTerms) {
this.submitForm();
}
if (next.callback) {
next.callback();
}
this.handleMethodSelect(element);
});
}
submitForm() {
@ -78,33 +105,6 @@ class Payment {
document.getElementById("payment-form").submit();
}
displayTerms() {
let displayTermsModal = document.getElementById("displayTermsModal");
displayTermsModal.removeAttribute("style");
}
displaySignature() {
document.getElementById("signature-next-step").disabled = true;
let displaySignatureModal = document.getElementById(
"displaySignatureModal"
);
displaySignatureModal.removeAttribute("style");
const signaturePad = new SignaturePad(
document.getElementById("signature-pad"),
{
penColor: "rgb(0, 0, 0)"
}
);
signaturePad.onEnd = function(){
document.getElementById("signature-next-step").disabled = false;
};
this.signaturePad = signaturePad;
}
handle() {
document
@ -124,5 +124,6 @@ const signature = document.querySelector(
).content;
const terms = document.querySelector('meta[name="show-invoice-terms"]').content;
const rff = document.querySelector('meta[name="show-required-fields-form"]').content;
new Payment(Boolean(+signature), Boolean(+terms)).handle();
new Payment(Boolean(+terms), Boolean(+signature), Boolean(+rff)).handle();

View File

@ -0,0 +1,104 @@
<div
style="display: none"
id="displayRequiredFieldsModal"
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center"
x-data="{ open: true }"
>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 transition-opacity"
>
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<div
x-show="open"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
class="bg-white rounded-lg px-4 pt-5 pb-4 overflow-hidden shadow-xl transform transition-all sm:max-w-lg sm:w-full sm:p-6"
>
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10"
>
<svg
class="h-6 w-6 text-red-600"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900">
{{ ctrans('texts.details') }}
</h3>
<div class="mt-2">
@if(strlen(auth()->guard('contact')->user()->first_name) === 0)
<div class="col-span-6 sm:col-span-3">
<label for="first_name" class="input-label">{{ ctrans('texts.first_name') }}</label>
<input id="first_name" class="input w-full" name="rff_first_name" value="{{ auth()->guard('contact')->user()->first_name }}" />
</div>
@endif
@if(strlen(auth()->guard('contact')->user()->last_name) === 0)
<div class="col-span-6 sm:col-span-3">
<label for="last_name" class="input-label">{{ ctrans('texts.last_name') }}</label>
<input id="last_name" class="input w-full" name="rff_last_name" value="{{ auth()->guard('contact')->user()->last_name }}"/>
</div>
@endif
@if(strlen(auth()->guard('contact')->user()->email) === 0)
<div class="col-span-6 sm:col-span-3">
<label for="email" class="input-label">{{ ctrans('texts.email') }}</label>
<input id="email" class="input w-full" name="rff_email" value="{{ auth()->guard('contact')->user()->email }}"/>
</div>
@endif
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<div
class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto"
x-data
>
<button
type="button"
id="rff-next-step"
class="button button-primary bg-primary"
>
{{ ctrans('texts.next_step') }}
</button>
</div>
<div
class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"
x-data
>
<button
@click="document.getElementById('displayRequiredFieldsModal').style.display = 'none';"
type="button"
class="button button-secondary"
id="close-button"
>
{{ ctrans('texts.close') }}
</button>
</div>
</div>
</div>
</div>

View File

@ -4,6 +4,7 @@
@push('head')
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
<meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}">
<meta name="show-required-fields-form" content="{{ auth()->guard('contact')->user()->showRff() }}" />
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
@endpush
@ -17,6 +18,9 @@
<input type="hidden" name="is_recurring" value="{{ isset($is_recurring) ? $is_recurring : false }}">
<input type="hidden" name="frequency_id" value="{{ isset($frequency_id) ? $frequency_id : false }}">
<input type="hidden" name="remaining_cycles" value="{{ isset($remaining_cycles) ? $remaining_cycles : false }}">
<input type="hidden" name="contact_first_name" value="{{ auth()->guard('contact')->user()->first_name }}">
<input type="hidden" name="contact_last_name" value="{{ auth()->guard('contact')->user()->last_name }}">
<input type="hidden" name="contact_email" value="{{ auth()->guard('contact')->user()->email }}">
<div class="container mx-auto">
<div class="grid grid-cols-6 gap-4">
@ -150,6 +154,7 @@
</div>
</form>
@include('portal.ninja2020.invoices.includes.required-fields')
@include('portal.ninja2020.invoices.includes.terms', ['entities' => $invoices, 'variables' => $variables, 'entity_type' => ctrans('texts.invoice')])
@include('portal.ninja2020.invoices.includes.signature')