diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php
--- a/src/app/Http/Controllers/API/SignupController.php
+++ b/src/app/Http/Controllers/API/SignupController.php
@@ -12,6 +12,7 @@
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
use App\SignupCode;
+use App\SignupInvitation;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
@@ -113,6 +114,33 @@
return response()->json(['status' => 'success', 'code' => $code->code]);
}
+ /**
+ * Returns signup invitation information.
+ *
+ * @param string $id Signup invitation identifier
+ *
+ * @return \Illuminate\Http\JsonResponse|void
+ */
+ public function invitation($id)
+ {
+ $invitation = SignupInvitation::withEnvTenant()->find($id);
+
+ if (empty($invitation) || $invitation->isCompleted()) {
+ return $this->errorResponse(404);
+ }
+
+ $has_domain = $this->getPlan()->hasDomain();
+
+ $result = [
+ 'id' => $id,
+ 'is_domain' => $has_domain,
+ 'domains' => $has_domain ? [] : Domain::getPublicDomains(),
+ ];
+
+ return response()->json($result);
+ }
+
+
/**
* Validation of the verification code.
*
@@ -190,10 +218,50 @@
return response()->json(['status' => 'error', 'errors' => $v->errors()], 422);
}
- // Validate verification codes (again)
- $v = $this->verify($request);
- if ($v->status() !== 200) {
- return $v;
+ // Signup via invitation
+ if ($request->invitation) {
+ $invitation = SignupInvitation::withEnvTenant()->find($request->invitation);
+
+ if (empty($invitation) || $invitation->isCompleted()) {
+ return $this->errorResponse(404);
+ }
+
+ // Check required fields
+ $v = Validator::make(
+ $request->all(),
+ [
+ 'first_name' => 'max:128',
+ 'last_name' => 'max:128',
+ 'voucher' => 'max:32',
+ ]
+ );
+
+ $errors = $v->fails() ? $v->errors()->toArray() : [];
+
+ if (!empty($errors)) {
+ return response()->json(['status' => 'error', 'errors' => $errors], 422);
+ }
+
+ $settings = [
+ 'external_email' => $invitation->email,
+ 'first_name' => $request->first_name,
+ 'last_name' => $request->last_name,
+ ];
+ } else {
+ // Validate verification codes (again)
+ $v = $this->verify($request);
+ if ($v->status() !== 200) {
+ return $v;
+ }
+
+ // Get user name/email from the verification code database
+ $code_data = $v->getData();
+
+ $settings = [
+ 'external_email' => $code_data->email,
+ 'first_name' => $code_data->first_name,
+ 'last_name' => $code_data->last_name,
+ ];
}
// Find the voucher discount
@@ -219,10 +287,6 @@
return response()->json(['status' => 'error', 'errors' => $errors], 422);
}
- // Get user name/email from the verification code database
- $code_data = $v->getData();
- $user_email = $code_data->email;
-
// We allow only ASCII, so we can safely lower-case the email address
$login = Str::lower($login);
$domain_name = Str::lower($domain_name);
@@ -254,14 +318,19 @@
$user->assignPlan($plan, $domain);
// Save the external email and plan in user settings
- $user->setSettings([
- 'external_email' => $user_email,
- 'first_name' => $code_data->first_name,
- 'last_name' => $code_data->last_name,
- ]);
+ $user->setSettings($settings);
+
+ // Update the invitation
+ if (!empty($invitation)) {
+ $invitation->status = SignupInvitation::STATUS_COMPLETED;
+ $invitation->user_id = $user->id;
+ $invitation->save();
+ }
// Remove the verification code
- $this->code->delete();
+ if ($this->code) {
+ $this->code->delete();
+ }
DB::commit();
diff --git a/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php b/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php
new file mode 100644
--- /dev/null
+++ b/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php
@@ -0,0 +1,251 @@
+errorResponse(404);
+ }
+
+ /**
+ * Remove the specified invitation.
+ *
+ * @param int $id Invitation identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function destroy($id)
+ {
+ $invitation = SignupInvitation::withUserTenant()->find($id);
+
+ if (empty($invitation)) {
+ return $this->errorResponse(404);
+ }
+
+ $invitation->delete();
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => trans('app.signup-invitation-delete-success'),
+ ]);
+ }
+
+ /**
+ * Show the form for editing the specified resource.
+ *
+ * @param int $id Invitation identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function edit($id)
+ {
+ return $this->errorResponse(404);
+ }
+
+ /**
+ * Display a listing of the resource.
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function index()
+ {
+ $pageSize = 10;
+ $search = request()->input('search');
+ $page = intval(request()->input('page')) ?: 1;
+ $hasMore = false;
+
+ $result = SignupInvitation::withUserTenant()
+ ->latest()
+ ->limit($pageSize + 1)
+ ->offset($pageSize * ($page - 1));
+
+ if ($search) {
+ if (strpos($search, '@')) {
+ $result->where('email', $search);
+ } else {
+ $result->whereLike('email', $search);
+ }
+ }
+
+ $result = $result->get();
+
+ if (count($result) > $pageSize) {
+ $result->pop();
+ $hasMore = true;
+ }
+
+ $result = $result->map(function ($invitation) {
+ return $this->invitationToArray($invitation);
+ });
+
+ return response()->json([
+ 'status' => 'success',
+ 'list' => $result,
+ 'count' => count($result),
+ 'hasMore' => $hasMore,
+ 'page' => $page,
+ ]);
+ }
+
+ /**
+ * Resend the specified invitation.
+ *
+ * @param int $id Invitation identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function resend($id)
+ {
+ $invitation = SignupInvitation::withUserTenant()->find($id);
+
+ if (empty($invitation)) {
+ return $this->errorResponse(404);
+ }
+
+ if ($invitation->isFailed() || $invitation->isSent()) {
+ // Note: The email sending job will be dispatched by the observer
+ $invitation->status = SignupInvitation::STATUS_NEW;
+ $invitation->save();
+ }
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => trans('app.signup-invitation-resend-success'),
+ 'invitation' => $this->invitationToArray($invitation),
+ ]);
+ }
+
+ /**
+ * Store a newly created resource in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function store(Request $request)
+ {
+ $errors = [];
+ $invitations = [];
+
+ if (!empty($request->file) && is_object($request->file)) {
+ // Expected a text/csv file with multiple email addresses
+ if (!$request->file->isValid()) {
+ $errors = ['file' => [$request->file->getErrorMessage()]];
+ } else {
+ $fh = fopen($request->file->getPathname(), 'r');
+ $line_number = 0;
+ $error = null;
+
+ while ($line = fgetcsv($fh)) {
+ $line_number++;
+
+ if (count($line) >= 1 && $line[0]) {
+ $email = trim($line[0]);
+
+ if (strpos($email, '@')) {
+ $v = Validator::make(['email' => $email], ['email' => 'email:filter|required']);
+
+ if ($v->fails()) {
+ $args = ['email' => $email, 'line' => $line_number];
+ $error = trans('app.signup-invitations-csv-invalid-email', $args);
+ break;
+ }
+
+ $invitations[] = ['email' => $email];
+ }
+ }
+ }
+
+ fclose($fh);
+
+ if ($error) {
+ $errors = ['file' => $error];
+ } elseif (empty($invitations)) {
+ $errors = ['file' => trans('app.signup-invitations-csv-empty')];
+ }
+ }
+ } else {
+ // Expected 'email' field with an email address
+ $v = Validator::make($request->all(), ['email' => 'email|required']);
+
+ if ($v->fails()) {
+ $errors = $v->errors()->toArray();
+ } else {
+ $invitations[] = ['email' => $request->email];
+ }
+ }
+
+ if (!empty($errors)) {
+ return response()->json(['status' => 'error', 'errors' => $errors], 422);
+ }
+
+ $count = 0;
+ foreach ($invitations as $idx => $invitation) {
+ SignupInvitation::create($invitation);
+ $count++;
+ }
+
+ return response()->json([
+ 'status' => 'success',
+ 'message' => \trans_choice('app.signup-invitations-created', $count, ['count' => $count]),
+ 'count' => $count,
+ ]);
+ }
+
+ /**
+ * Display the specified resource.
+ *
+ * @param int $id Invitation identifier
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function show($id)
+ {
+ return $this->errorResponse(404);
+ }
+
+ /**
+ * Update the specified resource in storage.
+ *
+ * @param \Illuminate\Http\Request $request
+ * @param int $id
+ *
+ * @return \Illuminate\Http\JsonResponse
+ */
+ public function update(Request $request, $id)
+ {
+ return $this->errorResponse(404);
+ }
+
+ /**
+ * Convert an invitation object to an array for output
+ *
+ * @param \App\SignupInvitation $invitation The signup invitation object
+ *
+ * @return array
+ */
+ protected static function invitationToArray(SignupInvitation $invitation): array
+ {
+ return [
+ 'id' => $invitation->id,
+ 'email' => $invitation->email,
+ 'isNew' => $invitation->isNew(),
+ 'isSent' => $invitation->isSent(),
+ 'isFailed' => $invitation->isFailed(),
+ 'isCompleted' => $invitation->isCompleted(),
+ 'created' => $invitation->created_at->toDateTimeString(),
+ ];
+ }
+}
diff --git a/src/app/Jobs/SignupInvitationEmail.php b/src/app/Jobs/SignupInvitationEmail.php
new file mode 100644
--- /dev/null
+++ b/src/app/Jobs/SignupInvitationEmail.php
@@ -0,0 +1,75 @@
+invitation = $invitation;
+ }
+
+ /**
+ * Execute the job.
+ *
+ * @return void
+ */
+ public function handle()
+ {
+ Mail::to($this->invitation->email)->send(new SignupInvitationMail($this->invitation));
+
+ // Update invitation status
+ $this->invitation->status = SignupInvitation::STATUS_SENT;
+ $this->invitation->save();
+ }
+
+ /**
+ * The job failed to process.
+ *
+ * @param \Exception $exception
+ *
+ * @return void
+ */
+ public function failed(\Exception $exception)
+ {
+ if ($this->attempts() >= $this->tries) {
+ // Update invitation status
+ $this->invitation->status = SignupInvitation::STATUS_FAILED;
+ $this->invitation->save();
+ }
+ }
+}
diff --git a/src/app/Mail/SignupInvitation.php b/src/app/Mail/SignupInvitation.php
new file mode 100644
--- /dev/null
+++ b/src/app/Mail/SignupInvitation.php
@@ -0,0 +1,72 @@
+invitation = $invitation;
+ }
+
+ /**
+ * Build the message.
+ *
+ * @return $this
+ */
+ public function build()
+ {
+ $href = Utils::serviceUrl('/signup/invite/' . $this->invitation->id);
+
+ $this->view('emails.html.signup_invitation')
+ ->text('emails.plain.signup_invitation')
+ ->subject(__('mail.signupinvitation-subject', ['site' => \config('app.name')]))
+ ->with([
+ 'site' => \config('app.name'),
+ 'href' => $href,
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Render the mail template with fake data
+ *
+ * @param string $type Output format ('html' or 'text')
+ *
+ * @return string HTML or Plain Text output
+ */
+ public static function fakeRender(string $type = 'html'): string
+ {
+ $invitation = new SI([
+ 'email' => 'test@external.org',
+ ]);
+
+ $invitation->id = Utils::uuidStr();
+
+ $mail = new self($invitation);
+
+ return Helper::render($mail, $type);
+ }
+}
diff --git a/src/app/Observers/SignupInvitationObserver.php b/src/app/Observers/SignupInvitationObserver.php
new file mode 100644
--- /dev/null
+++ b/src/app/Observers/SignupInvitationObserver.php
@@ -0,0 +1,65 @@
+{$invitation->getKeyName()} = $allegedly_unique;
+ break;
+ }
+ }
+
+ $invitation->status = SI::STATUS_NEW;
+
+ $invitation->tenant_id = \config('app.tenant_id');
+ }
+
+ /**
+ * Handle the invitation "created" event.
+ *
+ * @param \App\SignupInvitation $invitation The invitation object
+ *
+ * @return void
+ */
+ public function created(SI $invitation)
+ {
+ \App\Jobs\SignupInvitationEmail::dispatch($invitation);
+ }
+
+ /**
+ * Handle the invitation "updated" event.
+ *
+ * @param \App\SignupInvitation $invitation The invitation object
+ *
+ * @return void
+ */
+ public function updated(SI $invitation)
+ {
+ $oldStatus = $invitation->getOriginal('status');
+
+ // Resend the invitation
+ if (
+ $invitation->status == SI::STATUS_NEW
+ && ($oldStatus == SI::STATUS_FAILED || $oldStatus == SI::STATUS_SENT)
+ ) {
+ \App\Jobs\SignupInvitationEmail::dispatch($invitation);
+ }
+ }
+}
diff --git a/src/app/Providers/AppServiceProvider.php b/src/app/Providers/AppServiceProvider.php
--- a/src/app/Providers/AppServiceProvider.php
+++ b/src/app/Providers/AppServiceProvider.php
@@ -36,6 +36,7 @@
\App\PackageSku::observe(\App\Observers\PackageSkuObserver::class);
\App\Plan::observe(\App\Observers\PlanObserver::class);
\App\SignupCode::observe(\App\Observers\SignupCodeObserver::class);
+ \App\SignupInvitation::observe(\App\Observers\SignupInvitationObserver::class);
\App\Sku::observe(\App\Observers\SkuObserver::class);
\App\Transaction::observe(\App\Observers\TransactionObserver::class);
\App\User::observe(\App\Observers\UserObserver::class);
@@ -84,5 +85,24 @@
/** @var Builder $this */
return $this->whereNull(($table ? "$table." : '') . 'tenant_id');
});
+
+ // Query builder 'whereLike' mocro
+ Builder::macro('whereLike', function (string $column, string $search, int $mode = 0) {
+ $search = addcslashes($search, '%_');
+
+ switch ($mode) {
+ case 2:
+ $search .= '%';
+ break;
+ case 1:
+ $search = '%' . $search;
+ break;
+ default:
+ $search = '%' . $search . '%';
+ }
+
+ /** @var Builder $this */
+ return $this->where($column, 'like', $search);
+ });
}
}
diff --git a/src/app/SignupInvitation.php b/src/app/SignupInvitation.php
new file mode 100644
--- /dev/null
+++ b/src/app/SignupInvitation.php
@@ -0,0 +1,109 @@
+status & self::STATUS_COMPLETED) > 0;
+ }
+
+ /**
+ * Returns whether this invitation sending failed.
+ *
+ * @return bool
+ */
+ public function isFailed(): bool
+ {
+ return ($this->status & self::STATUS_FAILED) > 0;
+ }
+
+ /**
+ * Returns whether this invitation is new.
+ *
+ * @return bool
+ */
+ public function isNew(): bool
+ {
+ return ($this->status & self::STATUS_NEW) > 0;
+ }
+
+ /**
+ * Returns whether this invitation has been sent.
+ *
+ * @return bool
+ */
+ public function isSent(): bool
+ {
+ return ($this->status & self::STATUS_SENT) > 0;
+ }
+
+ /**
+ * The tenant for this invitation.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function tenant()
+ {
+ return $this->belongsTo('App\Tenant', 'tenant_id', 'id');
+ }
+
+ /**
+ * The account to which the invitation was used for.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
+ */
+ public function user()
+ {
+ return $this->belongsTo('App\User', 'user_id', 'id');
+ }
+}
diff --git a/src/app/Tenant.php b/src/app/Tenant.php
--- a/src/app/Tenant.php
+++ b/src/app/Tenant.php
@@ -27,4 +27,14 @@
{
return $this->hasMany('App\Discount');
}
+
+ /**
+ * SignupInvitations assigned to this tenant.
+ *
+ * @return \Illuminate\Database\Eloquent\Relations\HasMany
+ */
+ public function signupInvitations()
+ {
+ return $this->hasMany('App\SignupInvitation');
+ }
}
diff --git a/src/database/migrations/2021_03_26_080000_create_signup_invitations_table.php b/src/database/migrations/2021_03_26_080000_create_signup_invitations_table.php
new file mode 100644
--- /dev/null
+++ b/src/database/migrations/2021_03_26_080000_create_signup_invitations_table.php
@@ -0,0 +1,49 @@
+string('id', 36);
+ $table->string('email');
+ $table->smallInteger('status');
+ $table->bigInteger('user_id')->nullable();
+ $table->bigInteger('tenant_id')->unsigned()->nullable();
+ $table->timestamps();
+
+ $table->primary('id');
+
+ $table->index('email');
+ $table->index('created_at');
+
+ $table->foreign('tenant_id')->references('id')->on('tenants')
+ ->onUpdate('cascade')->onDelete('set null');
+ $table->foreign('user_id')->references('id')->on('users')
+ ->onUpdate('cascade')->onDelete('set null');
+ }
+ );
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('signup_invitations');
+ }
+}
diff --git a/src/phpstan.neon b/src/phpstan.neon
--- a/src/phpstan.neon
+++ b/src/phpstan.neon
@@ -7,10 +7,12 @@
- '#Access to an undefined property Illuminate\\Database\\Eloquent\\Model::\$id#'
- '#Access to an undefined property Illuminate\\Database\\Eloquent\\Model::\$created_at#'
- '#Call to an undefined method Illuminate\\Database\\Eloquent\\Model::toString\(\)#'
- - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder[^:]*::withEnvTenant\(\)#'
- - '#Call to an undefined method Illuminate\\Database\\Eloquent\\Builder[^:]*::withUserTenant\(\)#'
+ - '#Call to an undefined [a-zA-Z0-9<>\\ ]+::withEnvTenant\(\)#'
+ - '#Call to an undefined [a-zA-Z0-9<>\\ ]+::withUserTenant\(\)#'
- '#Call to an undefined method Tests\\Browser::#'
level: 4
+ parallel:
+ processTimeout: 300.0
paths:
- app/
- tests/
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -425,7 +425,7 @@
}
if (input.length) {
- // Create an error message\
+ // Create an error message
// API responses can use a string, array or object
let msg_text = ''
if ($.type(msg) !== 'string') {
diff --git a/src/resources/js/reseller/routes.js b/src/resources/js/reseller/routes.js
--- a/src/resources/js/reseller/routes.js
+++ b/src/resources/js/reseller/routes.js
@@ -1,5 +1,6 @@
import DashboardComponent from '../../vue/Reseller/Dashboard'
import DomainComponent from '../../vue/Admin/Domain'
+import InvitationsComponent from '../../vue/Reseller/Invitations'
import LoginComponent from '../../vue/Login'
import LogoutComponent from '../../vue/Logout'
import PageComponent from '../../vue/Page'
@@ -33,6 +34,12 @@
name: 'logout',
component: LogoutComponent
},
+ {
+ path: '/invitations',
+ name: 'invitations',
+ component: InvitationsComponent,
+ meta: { requiresAuth: true }
+ },
/*
{
path: '/stats',
diff --git a/src/resources/js/user/routes.js b/src/resources/js/user/routes.js
--- a/src/resources/js/user/routes.js
+++ b/src/resources/js/user/routes.js
@@ -76,6 +76,11 @@
component: MeetComponent,
meta: { requiresAuth: true }
},
+ {
+ path: '/signup/invite/:param',
+ name: 'signup-invite',
+ component: SignupComponent
+ },
{
path: '/signup/:param?',
alias: '/signup/voucher/:param',
diff --git a/src/resources/lang/en/app.php b/src/resources/lang/en/app.php
--- a/src/resources/lang/en/app.php
+++ b/src/resources/lang/en/app.php
@@ -45,6 +45,12 @@
'search-foundxdomains' => ':x domains have been found.',
'search-foundxusers' => ':x user accounts have been found.',
+ 'signup-invitations-created' => 'The invitation has been created.|:count invitations has been created.',
+ 'signup-invitations-csv-empty' => 'Failed to find any valid email addresses in the uploaded file.',
+ 'signup-invitations-csv-invalid-email' => 'Found an invalid email address (:email) on line :line.',
+ 'signup-invitation-delete-success' => 'Invitation deleted successfully.',
+ 'signup-invitation-resend-success' => 'Invitation added to the sending queue successfully.',
+
'support-request-success' => 'Support request submitted successfully.',
'support-request-error' => 'Failed to submit the support request.',
diff --git a/src/resources/lang/en/mail.php b/src/resources/lang/en/mail.php
--- a/src/resources/lang/en/mail.php
+++ b/src/resources/lang/en/mail.php
@@ -75,6 +75,11 @@
'signupcode-body1' => "This is your verification code for the :site registration process:",
'signupcode-body2' => "You can also click the link below to continue the registration process:",
+ 'signupinvitation-subject' => ":site Invitation",
+ 'signupinvitation-header' => "Hi,",
+ 'signupinvitation-body1' => "You have been invited to join :site. Click the link below to sign up.",
+ 'signupinvitation-body2' => "",
+
'suspendeddebtor-subject' => ":site Account Suspended",
'suspendeddebtor-body' => "You have been behind on paying for your :site account "
."for over :days days. Your account has been suspended.",
diff --git a/src/resources/themes/app.scss b/src/resources/themes/app.scss
--- a/src/resources/themes/app.scss
+++ b/src/resources/themes/app.scss
@@ -115,7 +115,10 @@
}
table {
- td.buttons,
+ th {
+ white-space: nowrap;
+ }
+
td.email,
td.price,
td.datetime,
@@ -124,6 +127,7 @@
white-space: nowrap;
}
+ td.buttons,
th.price,
td.price {
width: 1%;
@@ -279,6 +283,13 @@
opacity: 0.6;
}
+ // Some icons are too big, scale them down
+ &.link-invitations {
+ svg {
+ transform: scale(0.9);
+ }
+ }
+
.badge {
position: absolute;
top: 0.5rem;
diff --git a/src/resources/views/emails/html/signup_invitation.blade.php b/src/resources/views/emails/html/signup_invitation.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/html/signup_invitation.blade.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ {{ __('mail.signupinvitation-header') }}
+
+ {{ __('mail.signupinvitation-body1', ['site' => $site]) }}
+
+ {!! $href !!}
+
+ {{ __('mail.signupinvitation-body2') }}
+
+ {{ __('mail.footer1') }}
+ {{ __('mail.footer2', ['site' => $site]) }}
+
+
diff --git a/src/resources/views/emails/plain/signup_invitation.blade.php b/src/resources/views/emails/plain/signup_invitation.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/plain/signup_invitation.blade.php
@@ -0,0 +1,11 @@
+{!! __('mail.signupinvitation-header') !!}
+
+{!! __('mail.signupinvitation-body1', ['site' => $site]) !!}
+
+{!! $href !!}
+
+{!! __('mail.signupinvitation-body2') !!}
+
+--
+{!! __('mail.footer1') !!}
+{!! __('mail.footer2', ['site' => $site]) !!}
diff --git a/src/resources/vue/Reseller/Dashboard.vue b/src/resources/vue/Reseller/Dashboard.vue
--- a/src/resources/vue/Reseller/Dashboard.vue
+++ b/src/resources/vue/Reseller/Dashboard.vue
@@ -2,12 +2,19 @@
diff --git a/src/resources/vue/Signup.vue b/src/resources/vue/Signup.vue
--- a/src/resources/vue/Signup.vue
+++ b/src/resources/vue/Signup.vue
@@ -1,6 +1,6 @@
-
+
-
+
Sign Up - Step 1/3
@@ -39,7 +39,7 @@
-
+
Sign Up - Step 2/3
@@ -60,20 +60,28 @@
-
Sign Up - Step 3/3
+
Sign Up - Step 3/3
Create your Kolab identity (you can choose additional addresses later).
@@ -109,8 +119,10 @@
password: '',
password_confirmation: '',
domain: '',
- plan: null,
+ domains: [],
+ invitation: null,
is_domain: false,
+ plan: null,
plan_icons: {
individual: 'user',
group: 'users'
@@ -122,7 +134,25 @@
mounted() {
let param = this.$route.params.param;
- if (param) {
+ if (this.$route.name == 'signup-invite') {
+ this.$root.startLoading()
+ axios.get('/api/auth/signup/invitations/' + param)
+ .then(response => {
+ this.invitation = response.data
+ this.login = response.data.login
+ this.voucher = response.data.voucher
+ this.first_name = response.data.first_name
+ this.last_name = response.data.last_name
+ this.plan = response.data.plan
+ this.is_domain = response.data.is_domain
+ this.setDomain(response.data)
+ this.$root.stopLoading()
+ this.displayForm(3, true)
+ })
+ .catch(error => {
+ this.$root.errorHandler(error)
+ })
+ } else if (param) {
if (this.$route.path.indexOf('/signup/voucher/') === 0) {
// Voucher (discount) code
this.voucher = param
@@ -199,13 +229,7 @@
// Fill the domain selector with available domains
if (!this.is_domain) {
- let options = []
- $('select#signup_domain').html('')
- $.each(response.data.domains, (i, v) => {
- options.push($('