Appsumo (#232)
* Implemented webhooks * oAuth wip * Implement the whole auth flow * Implement file upload limit depending on appsumo license
This commit is contained in:
parent
2e52518aa7
commit
e9174238e4
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\License;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\AuthenticationException;
|
||||||
|
use Illuminate\Foundation\Auth\AuthenticatesUsers;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class AppSumoAuthController extends Controller
|
||||||
|
{
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
public function handleCallback(Request $request)
|
||||||
|
{
|
||||||
|
$this->validate($request, [
|
||||||
|
'code' => 'required',
|
||||||
|
]);
|
||||||
|
$accessToken = $this->retrieveAccessToken($request->code);
|
||||||
|
$license = $this->fetchOrCreateLicense($accessToken);
|
||||||
|
|
||||||
|
// If user connected, attach license
|
||||||
|
if (Auth::check()) return $this->attachLicense($license);
|
||||||
|
|
||||||
|
// otherwise start login flow by passing the encrypted license key id
|
||||||
|
if (is_null($license->user_id)) {
|
||||||
|
return redirect(url('/register?appsumo_license='.encrypt($license->id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect(url('/register?appsumo_error=1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function retrieveAccessToken(string $requestCode): string
|
||||||
|
{
|
||||||
|
return Http::withHeaders([
|
||||||
|
'Content-type' => 'application/json'
|
||||||
|
])->post('https://appsumo.com/openid/token/', [
|
||||||
|
'grant_type' => 'authorization_code',
|
||||||
|
'code' => $requestCode,
|
||||||
|
'redirect_uri' => route('appsumo.callback'),
|
||||||
|
'client_id' => config('services.appsumo.client_id'),
|
||||||
|
'client_secret' => config('services.appsumo.client_secret'),
|
||||||
|
])->throw()->json('access_token');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function fetchOrCreateLicense(string $accessToken): License
|
||||||
|
{
|
||||||
|
// Fetch license from API
|
||||||
|
$licenseKey = Http::get('https://appsumo.com/openid/license_key/?access_token=' . $accessToken)
|
||||||
|
->throw()
|
||||||
|
->json('license_key');
|
||||||
|
|
||||||
|
// Fetch or create license model
|
||||||
|
$license = License::where('license_provider','appsumo')->where('license_key',$licenseKey)->first();
|
||||||
|
if (!$license) {
|
||||||
|
$licenseData = Http::withHeaders([
|
||||||
|
'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
|
||||||
|
])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
|
||||||
|
|
||||||
|
// Create new license
|
||||||
|
$license = License::create([
|
||||||
|
'license_key' => $licenseKey,
|
||||||
|
'license_provider' => 'appsumo',
|
||||||
|
'status' => $licenseData['status'] === 'active' ? License::STATUS_ACTIVE : License::STATUS_INACTIVE,
|
||||||
|
'meta' => $licenseData,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $license;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function attachLicense(License $license) {
|
||||||
|
if (!Auth::check()) {
|
||||||
|
throw new AuthenticationException('User not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach license if not already attached
|
||||||
|
if (is_null($license->user_id)) {
|
||||||
|
$license->user_id = Auth::id();
|
||||||
|
$license->save();
|
||||||
|
return redirect(url('/home?appsumo_connect=1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Licensed already attached
|
||||||
|
return redirect(url('/home?appsumo_error=1'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param User $user
|
||||||
|
* @param string|null $licenseHash
|
||||||
|
* @return string|null
|
||||||
|
*
|
||||||
|
* Returns null if no license found
|
||||||
|
* Returns true if license was found and attached
|
||||||
|
* Returns false if there was an error (license not found or already attached)
|
||||||
|
*/
|
||||||
|
public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
|
||||||
|
{
|
||||||
|
if (!$licenseHash) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$licenseId = decrypt($licenseHash);
|
||||||
|
$license = License::find($licenseId);
|
||||||
|
|
||||||
|
if ($license && is_null($license->user_id)) {
|
||||||
|
$license->user_id = $user->id;
|
||||||
|
$license->save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace App\Http\Controllers\Auth;
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Resources\UserResource;
|
||||||
use App\Models\Workspace;
|
use App\Models\Workspace;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
@ -15,6 +16,8 @@ class RegisterController extends Controller
|
||||||
{
|
{
|
||||||
use RegistersUsers;
|
use RegistersUsers;
|
||||||
|
|
||||||
|
private ?bool $appsumoLicense = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new controller instance.
|
* Create a new controller instance.
|
||||||
*
|
*
|
||||||
|
@ -38,7 +41,11 @@ class RegisterController extends Controller
|
||||||
return response()->json(['status' => trans('verification.sent')]);
|
return response()->json(['status' => trans('verification.sent')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json($user);
|
return response()->json(array_merge(
|
||||||
|
(new UserResource($user))->toArray($request),
|
||||||
|
[
|
||||||
|
'appsumo_license' => $this->appsumoLicense,
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,7 +61,8 @@ class RegisterController extends Controller
|
||||||
'email' => 'required|email:filter|max:255|unique:users|indisposable',
|
'email' => 'required|email:filter|max:255|unique:users|indisposable',
|
||||||
'password' => 'required|min:6|confirmed',
|
'password' => 'required|min:6|confirmed',
|
||||||
'hear_about_us' => 'required|string',
|
'hear_about_us' => 'required|string',
|
||||||
'agree_terms' => ['required',Rule::in([true])]
|
'agree_terms' => ['required', Rule::in([true])],
|
||||||
|
'appsumo_license' => ['nullable'],
|
||||||
], [
|
], [
|
||||||
'agree_terms' => 'Please agree with the terms and conditions.'
|
'agree_terms' => 'Please agree with the terms and conditions.'
|
||||||
]);
|
]);
|
||||||
|
@ -87,6 +95,8 @@ class RegisterController extends Controller
|
||||||
]
|
]
|
||||||
], false);
|
], false);
|
||||||
|
|
||||||
|
$this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Webhook;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\License;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Validation\UnauthorizedException;
|
||||||
|
|
||||||
|
class AppSumoController extends Controller
|
||||||
|
{
|
||||||
|
public function handle(Request $request)
|
||||||
|
{
|
||||||
|
$this->validateSignature($request);
|
||||||
|
|
||||||
|
if ($request->test) {
|
||||||
|
return $this->success([
|
||||||
|
'message' => 'Webhook received.',
|
||||||
|
'event' => $request->event,
|
||||||
|
'success' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call the right function depending on the event using match()
|
||||||
|
match ($request->event) {
|
||||||
|
'activate' => $this->handleActivateEvent($request),
|
||||||
|
'upgrade', 'downgrade' => $this->handleChangeEvent($request),
|
||||||
|
'deactivate' => $this->handleDeactivateEvent($request),
|
||||||
|
default => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return $this->success([
|
||||||
|
'message' => 'Webhook received.',
|
||||||
|
'event' => $request->event,
|
||||||
|
'success' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleActivateEvent($request)
|
||||||
|
{
|
||||||
|
$licence = License::firstOrNew([
|
||||||
|
'license_key' => $request->license_key,
|
||||||
|
'license_provider' => 'appsumo',
|
||||||
|
'status' => License::STATUS_ACTIVE,
|
||||||
|
]);
|
||||||
|
$licence->meta = $request->json()->all();
|
||||||
|
$licence->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleChangeEvent($request)
|
||||||
|
{
|
||||||
|
// Deactivate old license
|
||||||
|
$oldLicense = License::where([
|
||||||
|
'license_key' => $request->prev_license_key,
|
||||||
|
'license_provider' => 'appsumo',
|
||||||
|
])->firstOrFail();
|
||||||
|
$oldLicense->update([
|
||||||
|
'status' => License::STATUS_INACTIVE,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Create new license
|
||||||
|
License::create([
|
||||||
|
'license_key' => $request->license_key,
|
||||||
|
'license_provider' => 'appsumo',
|
||||||
|
'status' => License::STATUS_ACTIVE,
|
||||||
|
'meta' => $request->json()->all(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleDeactivateEvent($request)
|
||||||
|
{
|
||||||
|
// Deactivate old license
|
||||||
|
$oldLicense = License::where([
|
||||||
|
'license_key' => $request->prev_license_key,
|
||||||
|
'license_provider' => 'appsumo',
|
||||||
|
])->firstOrFail();
|
||||||
|
$oldLicense->update([
|
||||||
|
'status' => License::STATUS_INACTIVE,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateSignature(Request $request)
|
||||||
|
{
|
||||||
|
$signature = $request->header('x-appsumo-signature');
|
||||||
|
$payload = $request->getContent();
|
||||||
|
|
||||||
|
if ($signature === hash_hmac('sha256', $payload, config('services.appsumo.api_key'))) {
|
||||||
|
throw new UnauthorizedException('Invalid signature.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,9 +16,6 @@ use App\Rules\ValidUrl;
|
||||||
|
|
||||||
class AnswerFormRequest extends FormRequest
|
class AnswerFormRequest extends FormRequest
|
||||||
{
|
{
|
||||||
const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
|
|
||||||
const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
|
|
||||||
|
|
||||||
public Form $form;
|
public Form $form;
|
||||||
|
|
||||||
protected array $requestRules = [];
|
protected array $requestRules = [];
|
||||||
|
@ -27,12 +24,7 @@ class AnswerFormRequest extends FormRequest
|
||||||
public function __construct(Request $request)
|
public function __construct(Request $request)
|
||||||
{
|
{
|
||||||
$this->form = $request->form;
|
$this->form = $request->form;
|
||||||
|
$this->maxFileSize = $this->form->workspace->max_file_size;
|
||||||
$this->maxFileSize = self::MAX_FILE_SIZE_FREE;
|
|
||||||
$workspace = $this->form->workspace;
|
|
||||||
if ($workspace && $workspace->is_pro) {
|
|
||||||
$this->maxFileSize = self::MAX_FILE_SIZE_PRO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -21,6 +21,7 @@ class UserResource extends JsonResource
|
||||||
'template_editor' => $this->template_editor,
|
'template_editor' => $this->template_editor,
|
||||||
'has_customer_id' => $this->has_customer_id,
|
'has_customer_id' => $this->has_customer_id,
|
||||||
'has_forms' => $this->has_forms,
|
'has_forms' => $this->has_forms,
|
||||||
|
'active_license' => $this->licenses()->active()->first(),
|
||||||
] : [];
|
] : [];
|
||||||
|
|
||||||
return array_merge(parent::toArray($request), $personalData);
|
return array_merge(parent::toArray($request), $personalData);
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class License extends Model
|
||||||
|
{
|
||||||
|
const STATUS_ACTIVE = 'active';
|
||||||
|
const STATUS_INACTIVE = 'inactive';
|
||||||
|
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'license_key',
|
||||||
|
'user_id',
|
||||||
|
'license_provider',
|
||||||
|
'status',
|
||||||
|
'meta'
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'meta' => 'array',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function scopeActive($query)
|
||||||
|
{
|
||||||
|
return $query->where('status', self::STATUS_ACTIVE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMaxFileSizeAttribute()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
1 => 25000000, // 25 MB,
|
||||||
|
2 => 50000000, // 50 MB,
|
||||||
|
3 => 75000000, // 75 MB,
|
||||||
|
][$this->meta['tier']];
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Laravel\Cashier\Billable;
|
use Laravel\Cashier\Billable;
|
||||||
use Tymon\JWTAuth\Contracts\JWTSubject;
|
use Tymon\JWTAuth\Contracts\JWTSubject;
|
||||||
|
|
||||||
class User extends Authenticatable implements JWTSubject
|
class User extends Authenticatable implements JWTSubject
|
||||||
{
|
{
|
||||||
use Notifiable, HasFactory, Billable;
|
use Notifiable, HasFactory, Billable;
|
||||||
|
@ -80,7 +81,9 @@ class User extends Authenticatable implements JWTSubject
|
||||||
|
|
||||||
public function getIsSubscribedAttribute()
|
public function getIsSubscribedAttribute()
|
||||||
{
|
{
|
||||||
return $this->subscribed() || in_array($this->email, config('opnform.extra_pro_users_emails'));
|
return $this->subscribed()
|
||||||
|
|| in_array($this->email, config('opnform.extra_pro_users_emails'))
|
||||||
|
|| !is_null($this->activeLicense());
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHasCustomerIdAttribute()
|
public function getHasCustomerIdAttribute()
|
||||||
|
@ -146,6 +149,16 @@ class User extends Authenticatable implements JWTSubject
|
||||||
return $this->hasMany(Template::class, 'creator_id');
|
return $this->hasMany(Template::class, 'creator_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function licenses()
|
||||||
|
{
|
||||||
|
return $this->hasMany(License::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function activeLicense(): License
|
||||||
|
{
|
||||||
|
return $this->licenses()->active()->first();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* =================================
|
* =================================
|
||||||
* Oauth Related
|
* Oauth Related
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Http\Requests\AnswerFormRequest;
|
||||||
use App\Models\Forms\Form;
|
use App\Models\Forms\Form;
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
@ -11,6 +11,9 @@ class Workspace extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
|
||||||
|
const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name',
|
'name',
|
||||||
'icon',
|
'icon',
|
||||||
|
@ -37,6 +40,26 @@ class Workspace extends Model
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getMaxFileSizeAttribute()
|
||||||
|
{
|
||||||
|
if(is_null(config('cashier.key'))){
|
||||||
|
return self::MAX_FILE_SIZE_PRO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return max file size depending on subscription
|
||||||
|
foreach ($this->owners as $owner) {
|
||||||
|
if ($owner->is_subscribed) {
|
||||||
|
if ($license = $owner->activeLicense()) {
|
||||||
|
// In case of special License
|
||||||
|
return $license->max_file_size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return self::MAX_FILE_SIZE_PRO;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::MAX_FILE_SIZE_FREE;
|
||||||
|
}
|
||||||
|
|
||||||
public function getIsEnterpriseAttribute()
|
public function getIsEnterpriseAttribute()
|
||||||
{
|
{
|
||||||
if(is_null(config('cashier.key'))){
|
if(is_null(config('cashier.key'))){
|
||||||
|
|
|
@ -57,6 +57,12 @@ return [
|
||||||
'secret_key' => env('UNSPLASH_SECRET_KEY'),
|
'secret_key' => env('UNSPLASH_SECRET_KEY'),
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'appsumo' => [
|
||||||
|
'client_id' => env('APPSUMO_CLIENT_ID'),
|
||||||
|
'client_secret' => env('APPSUMO_CLIENT_SECRET'),
|
||||||
|
'api_key' => env('APPSUMO_API_KEY'),
|
||||||
|
],
|
||||||
|
|
||||||
'google_analytics_code' => env('GOOGLE_ANALYTICS_CODE'),
|
'google_analytics_code' => env('GOOGLE_ANALYTICS_CODE'),
|
||||||
'amplitude_code' => env('AMPLITUDE_CODE'),
|
'amplitude_code' => env('AMPLITUDE_CODE'),
|
||||||
'crisp_website_id' => env('CRISP_WEBSITE_ID'),
|
'crisp_website_id' => env('CRISP_WEBSITE_ID'),
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('licenses', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('license_key');
|
||||||
|
$table->unsignedBigInteger('user_id')->nullable();
|
||||||
|
$table->string('license_provider');
|
||||||
|
$table->string('status');
|
||||||
|
$table->json('meta');
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->index(['license_key', 'license_provider']);
|
||||||
|
$table->index(['user_id', 'license_provider']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('licenses');
|
||||||
|
}
|
||||||
|
};
|
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
|
@ -11,20 +11,20 @@
|
||||||
</span>
|
</span>
|
||||||
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
stroke-linejoin="round"/>
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<loader v-else class="h-6 w-6 mx-auto" :class="`text-${colorShades['text']}`" />
|
<loader v-else class="h-6 w-6 mx-auto" :class="`text-${colorShades['text']}`" />
|
||||||
</button>
|
</button>
|
||||||
<router-link v-else :class="btnClasses" :to="to" :target="target"
|
<router-link v-else :class="btnClasses" :to="to" :target="target">
|
||||||
>
|
|
||||||
<span class="no-underline mx-auto">
|
<span class="no-underline mx-auto">
|
||||||
<slot />
|
<slot />
|
||||||
</span>
|
</span>
|
||||||
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg v-if="arrow" class="ml-2 w-3 h-3 inline" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
<path d="M1 11L11 1M11 1H1M11 1V11" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
stroke-linejoin="round"/>
|
stroke-linejoin="round"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</template>
|
||||||
|
@ -72,7 +72,7 @@ export default {
|
||||||
target: {
|
target: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '_self'
|
default: '_self'
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -91,7 +91,7 @@ export default {
|
||||||
hover: 'hover:bg-blue-700',
|
hover: 'hover:bg-blue-700',
|
||||||
ring: 'focus:ring-blue-500',
|
ring: 'focus:ring-blue-500',
|
||||||
'ring-offset': 'focus:ring-offset-blue-200',
|
'ring-offset': 'focus:ring-offset-blue-200',
|
||||||
text: 'text-white',
|
text: 'text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'outline-blue') {
|
} else if (this.color === 'outline-blue') {
|
||||||
return {
|
return {
|
||||||
|
@ -99,7 +99,15 @@ export default {
|
||||||
hover: 'hover:bg-blue-600',
|
hover: 'hover:bg-blue-600',
|
||||||
ring: 'focus:ring-blue-500',
|
ring: 'focus:ring-blue-500',
|
||||||
'ring-offset': 'focus:ring-offset-blue-200',
|
'ring-offset': 'focus:ring-offset-blue-200',
|
||||||
text: 'text-blue-600 hover:text-white',
|
text: 'text-blue-600 hover:text-white'
|
||||||
|
}
|
||||||
|
} else if (this.color === 'outline-gray') {
|
||||||
|
return {
|
||||||
|
main: 'bg-transparent border border-gray-300',
|
||||||
|
hover: 'hover:bg-gray-500',
|
||||||
|
ring: 'focus:ring-gray-500',
|
||||||
|
'ring-offset': 'focus:ring-offset-gray-200',
|
||||||
|
text: 'text-gray-500 hover:text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'red') {
|
} else if (this.color === 'red') {
|
||||||
return {
|
return {
|
||||||
|
@ -107,7 +115,7 @@ export default {
|
||||||
hover: 'hover:bg-red-700',
|
hover: 'hover:bg-red-700',
|
||||||
ring: 'focus:ring-red-500',
|
ring: 'focus:ring-red-500',
|
||||||
'ring-offset': 'focus:ring-offset-red-200',
|
'ring-offset': 'focus:ring-offset-red-200',
|
||||||
text: 'text-white',
|
text: 'text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'gray') {
|
} else if (this.color === 'gray') {
|
||||||
return {
|
return {
|
||||||
|
@ -115,7 +123,7 @@ export default {
|
||||||
hover: 'hover:bg-gray-700',
|
hover: 'hover:bg-gray-700',
|
||||||
ring: 'focus:ring-gray-500',
|
ring: 'focus:ring-gray-500',
|
||||||
'ring-offset': 'focus:ring-offset-gray-200',
|
'ring-offset': 'focus:ring-offset-gray-200',
|
||||||
text: 'text-white',
|
text: 'text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'light-gray') {
|
} else if (this.color === 'light-gray') {
|
||||||
return {
|
return {
|
||||||
|
@ -123,7 +131,7 @@ export default {
|
||||||
hover: 'hover:bg-gray-100',
|
hover: 'hover:bg-gray-100',
|
||||||
ring: 'focus:ring-gray-500',
|
ring: 'focus:ring-gray-500',
|
||||||
'ring-offset': 'focus:ring-offset-gray-300',
|
'ring-offset': 'focus:ring-offset-gray-300',
|
||||||
text: 'text-gray-700',
|
text: 'text-gray-700'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'green') {
|
} else if (this.color === 'green') {
|
||||||
return {
|
return {
|
||||||
|
@ -131,7 +139,7 @@ export default {
|
||||||
hover: 'hover:bg-green-700',
|
hover: 'hover:bg-green-700',
|
||||||
ring: 'focus:ring-green-500',
|
ring: 'focus:ring-green-500',
|
||||||
'ring-offset': 'focus:ring-offset-green-200',
|
'ring-offset': 'focus:ring-offset-green-200',
|
||||||
text: 'text-white',
|
text: 'text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'yellow') {
|
} else if (this.color === 'yellow') {
|
||||||
return {
|
return {
|
||||||
|
@ -139,7 +147,7 @@ export default {
|
||||||
hover: 'hover:bg-yellow-700',
|
hover: 'hover:bg-yellow-700',
|
||||||
ring: 'focus:ring-yellow-500',
|
ring: 'focus:ring-yellow-500',
|
||||||
'ring-offset': 'focus:ring-offset-yellow-200',
|
'ring-offset': 'focus:ring-offset-yellow-200',
|
||||||
text: 'text-white',
|
text: 'text-white'
|
||||||
}
|
}
|
||||||
} else if (this.color === 'white') {
|
} else if (this.color === 'white') {
|
||||||
return {
|
return {
|
||||||
|
@ -147,7 +155,7 @@ export default {
|
||||||
hover: 'hover:bg-gray-200',
|
hover: 'hover:bg-gray-200',
|
||||||
ring: 'focus:ring-white-500',
|
ring: 'focus:ring-white-500',
|
||||||
'ring-offset': 'focus:ring-offset-white-200',
|
'ring-offset': 'focus:ring-offset-white-200',
|
||||||
text: 'text-gray-700',
|
text: 'text-gray-700'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.error('Unknown color')
|
console.error('Unknown color')
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="user.active_license" class="border p-5 shadow-md rounded-md">
|
||||||
|
<div class="w-auto flex flex-col items-center">
|
||||||
|
<img :src="asset('img/appsumo/as-taco-white-bg.png')" class="max-w-[60px]" alt="AppSumo">
|
||||||
|
<img :src="asset('img/appsumo/as-Select-dark.png')" class="max-w-[150px]" alt="AppSumo">
|
||||||
|
</div>
|
||||||
|
<p class="mt-6">
|
||||||
|
Your AppSumo <span class="font-semibold">lifetime deal tier {{ licenseTier }}</span> license is active. Here's a reminder of your plan details:
|
||||||
|
</p>
|
||||||
|
<ul class="list-disc pl-5 mt-4">
|
||||||
|
<li>Number of Forms: <span class="font-semibold">{{ tierFeatures.form_quantity }}</span></li>
|
||||||
|
<li>Custom domains: <span class="font-semibold">{{ tierFeatures.domain_names }}</span></li>
|
||||||
|
<li>File Size Uploads: <span class="font-semibold">{{ tierFeatures.file_upload_size }}</span></li>
|
||||||
|
</ul>
|
||||||
|
<div class="w-max">
|
||||||
|
<v-button color="outline-gray" shade="lighter" class="mt-4 block" href="https://appsumo.com/account/products/" target="_blank">
|
||||||
|
Mangage in AppSumo
|
||||||
|
</v-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import VButton from '../../common/Button.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
name: 'AppSumoBilling',
|
||||||
|
components: { VButton },
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
user: 'auth/user'
|
||||||
|
}),
|
||||||
|
licenseTier () {
|
||||||
|
return this.user?.active_license?.meta?.tier
|
||||||
|
},
|
||||||
|
tierFeatures () {
|
||||||
|
if (!this.licenseTier) return {}
|
||||||
|
return {
|
||||||
|
1: {
|
||||||
|
form_quantity: 'Unlimited',
|
||||||
|
file_upload_size: '25mb',
|
||||||
|
domain_names: '5'
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
form_quantity: 'Unlimited',
|
||||||
|
file_upload_size: '50mb',
|
||||||
|
domain_names: '25'
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
form_quantity: 'Unlimited',
|
||||||
|
file_upload_size: '75mb',
|
||||||
|
domain_names: 'Unlimited'
|
||||||
|
}
|
||||||
|
}[this.licenseTier]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {},
|
||||||
|
|
||||||
|
mounted () {},
|
||||||
|
|
||||||
|
created () {
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -0,0 +1,50 @@
|
||||||
|
<template>
|
||||||
|
<div v-if="hasValidLicense" class="p-6 bg-white border shadow-md rounded-md">
|
||||||
|
<img :src="asset('img/appsumo/as-taco-white-bg.png')" class="max-w-[60px] mx-auto" alt="AppSumo">
|
||||||
|
<img :src="asset('img/appsumo/as-Select-dark.png')" class="max-w-[300px] mx-auto" alt="AppSumo">
|
||||||
|
<p class="mt-6">
|
||||||
|
<span class="text-green-500">We found your AppSumo Lifetime deal license!</span> Just complete the registration form to finalize the activation of
|
||||||
|
your license.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hasLicenseError" class="p-6 bg-white border border-red-500 shadow-md rounded-md">
|
||||||
|
<img :src="asset('img/appsumo/as-taco-white-bg.png')" class="max-w-[60px] mx-auto" alt="AppSumo">
|
||||||
|
<img :src="asset('img/appsumo/as-Select-dark.png')" class="max-w-[300px] mx-auto" alt="AppSumo">
|
||||||
|
<p class="mt-6">
|
||||||
|
<span class="text-red-600">Invalid AppSumo license</span>. The license was probably already attached to an OpnForm account. Please contact support.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
|
||||||
|
name: 'AppSumoRegister',
|
||||||
|
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
hasValidLicense: false,
|
||||||
|
hasLicenseError: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {},
|
||||||
|
|
||||||
|
watch: {},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
if (this.$route.query.appsumo_license !== undefined && this.$route.query.appsumo_license) {
|
||||||
|
this.hasValidLicense = true
|
||||||
|
} else if (this.$route.query.appsumo_error !== undefined && this.$route.query.appsumo_error) {
|
||||||
|
this.hasLicenseError = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created () {
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<form @submit.prevent="register" @keydown="form.onKeydown($event)" class="mt-4">
|
<form class="mt-4" @submit.prevent="register" @keydown="form.onKeydown($event)">
|
||||||
<!-- Name -->
|
<!-- Name -->
|
||||||
<text-input name="name" :form="form" :label="$t('name')" placeholder="Your name" :required="true" />
|
<text-input name="name" :form="form" :label="$t('name')" placeholder="Your name" :required="true" />
|
||||||
|
|
||||||
|
@ -23,37 +23,37 @@
|
||||||
|
|
||||||
<checkbox-input :form="form" name="agree_terms" :required="true">
|
<checkbox-input :form="form" name="agree_terms" :required="true">
|
||||||
<template #label>
|
<template #label>
|
||||||
I agree with the <router-link :to="{name:'terms-conditions'}" target="_blank">Terms and conditions</router-link> and <router-link :to="{name:'privacy-policy'}" target="_blank">Privacy policy</router-link> of the website and I accept them.
|
I agree with the <router-link :to="{name:'terms-conditions'}" target="_blank">
|
||||||
|
Terms and conditions
|
||||||
|
</router-link> and <router-link :to="{name:'privacy-policy'}" target="_blank">
|
||||||
|
Privacy policy
|
||||||
|
</router-link> of the website and I accept them.
|
||||||
</template>
|
</template>
|
||||||
</checkbox-input>
|
</checkbox-input>
|
||||||
|
|
||||||
<!-- Submit Button -->
|
<!-- Submit Button -->
|
||||||
<v-button :loading="form.busy">Create an account</v-button>
|
<v-button :loading="form.busy">
|
||||||
|
Create an account
|
||||||
|
</v-button>
|
||||||
|
|
||||||
<p class="text-gray-500 mt-4">
|
<p class="text-gray-500 mt-4">
|
||||||
Already have an account?
|
Already have an account?
|
||||||
<a href="#" v-if="isQuick" @click.prevent="$emit('openLogin')" class="font-semibold ml-1">Log In</a>
|
<a v-if="isQuick" href="#" class="font-semibold ml-1" @click.prevent="$emit('openLogin')">Log In</a>
|
||||||
<router-link v-else :to="{name:'login'}" class="font-semibold ml-1">Log In</router-link>
|
<router-link v-else :to="{name:'login'}" class="font-semibold ml-1">
|
||||||
|
Log In
|
||||||
|
</router-link>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- GitHub Register Button -->
|
|
||||||
<login-with-github />
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Form from 'vform'
|
import Form from 'vform'
|
||||||
import LoginWithGithub from '~/components/LoginWithGithub.vue'
|
|
||||||
import SelectInput from '../../../components/forms/SelectInput.vue'
|
|
||||||
import { initCrisp } from '../../../middleware/check-auth.js'
|
import { initCrisp } from '../../../middleware/check-auth.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'RegisterForm',
|
name: 'RegisterForm',
|
||||||
components: {
|
components: {},
|
||||||
SelectInput,
|
|
||||||
LoginWithGithub,
|
|
||||||
},
|
|
||||||
props: {
|
props: {
|
||||||
isQuick: {
|
isQuick: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
|
@ -68,7 +68,8 @@ export default {
|
||||||
email: '',
|
email: '',
|
||||||
password: '',
|
password: '',
|
||||||
password_confirmation: '',
|
password_confirmation: '',
|
||||||
agree_terms: false
|
agree_terms: false,
|
||||||
|
appsumo_license: null
|
||||||
}),
|
}),
|
||||||
mustVerifyEmail: false
|
mustVerifyEmail: false
|
||||||
}),
|
}),
|
||||||
|
@ -91,6 +92,13 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mounted () {
|
||||||
|
// Set appsumo license
|
||||||
|
if (this.$route.query.appsumo_license !== undefined && this.$route.query.appsumo_license) {
|
||||||
|
this.form.appsumo_license = this.$route.query.appsumo_license
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async register () {
|
async register () {
|
||||||
// Register the user.
|
// Register the user.
|
||||||
|
@ -115,6 +123,15 @@ export default {
|
||||||
initCrisp(data)
|
initCrisp(data)
|
||||||
this.$crisp.push(['set', 'session:event', [[['register', {}, 'blue']]]])
|
this.$crisp.push(['set', 'session:event', [[['register', {}, 'blue']]]])
|
||||||
|
|
||||||
|
// AppSumo License
|
||||||
|
if (data.appsumo_license === false) {
|
||||||
|
this.alertError('Invalid AppSumo license. This probably happened because this license was already' +
|
||||||
|
' attached to another OpnForm account. Please contact support.')
|
||||||
|
} else if (data.appsumo_license === true) {
|
||||||
|
this.alertSuccess('Your AppSumo license was successfully activated! You now have access to all the' +
|
||||||
|
' features of the AppSumo deal.')
|
||||||
|
}
|
||||||
|
|
||||||
// Redirect
|
// Redirect
|
||||||
if (this.isQuick) {
|
if (this.isQuick) {
|
||||||
this.$emit('afterQuickLogin')
|
this.$emit('afterQuickLogin')
|
||||||
|
|
|
@ -3,16 +3,17 @@
|
||||||
<div class="flex mt-6 mb-10">
|
<div class="flex mt-6 mb-10">
|
||||||
<div class="w-full md:max-w-6xl mx-auto px-4 flex items-center md:flex-row-reverse flex-wrap">
|
<div class="w-full md:max-w-6xl mx-auto px-4 flex items-center md:flex-row-reverse flex-wrap">
|
||||||
<div class="w-full lg:w-1/2 md:p-6">
|
<div class="w-full lg:w-1/2 md:p-6">
|
||||||
|
<app-sumo-register class="mb-10 p-6 lg:hidden" />
|
||||||
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
<div class="border rounded-md p-6 shadow-md sticky top-4">
|
||||||
<h2 class="font-semibold text-2xl">
|
<h2 class="font-semibold text-2xl">
|
||||||
Create an account
|
Create an account
|
||||||
</h2>
|
</h2>
|
||||||
<small>Sign up in less than 2 minutes.</small>
|
<small>Sign up in less than 2 minutes.</small>
|
||||||
|
|
||||||
<register-form />
|
<register-form />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full hidden lg:block lg:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
<div class="w-full hidden lg:block lg:w-1/2 md:p-6 mt-8 md:mt-0 ">
|
||||||
|
<app-sumo-register class="mb-10" />
|
||||||
<h1 class="font-bold">
|
<h1 class="font-bold">
|
||||||
Create beautiful forms and share them anywhere
|
Create beautiful forms and share them anywhere
|
||||||
</h1>
|
</h1>
|
||||||
|
@ -57,29 +58,27 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
import OpenFormFooter from '../../components/pages/OpenFormFooter.vue'
|
||||||
import Testimonials from '../../components/pages/welcome/Testimonials.vue'
|
|
||||||
import RegisterForm from './components/RegisterForm.vue'
|
import RegisterForm from './components/RegisterForm.vue'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
import SeoMeta from '../../mixins/seo-meta.js'
|
||||||
|
import AppSumoRegister from '../../components/vendor/appsumo/AppSumoRegister.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Testimonials,
|
AppSumoRegister,
|
||||||
OpenFormFooter,
|
OpenFormFooter,
|
||||||
RegisterForm
|
RegisterForm
|
||||||
},
|
},
|
||||||
|
|
||||||
middleware: 'guest',
|
|
||||||
|
|
||||||
mixins: [SeoMeta],
|
mixins: [SeoMeta],
|
||||||
|
|
||||||
|
middleware: 'guest',
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Register',
|
metaTitle: 'Register'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
computed: {
|
computed: {},
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-semibold text-2xl text-gray-900">Billing details</h3>
|
<h3 class="font-semibold text-2xl text-gray-900">
|
||||||
|
Billing details
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<template v-if="user.has_customer_id">
|
||||||
<small class="text-gray-600">Manage your billing. Download invoices, update your plan, or cancel it at any
|
<small class="text-gray-600">Manage your billing. Download invoices, update your plan, or cancel it at any
|
||||||
time.</small>
|
time.</small>
|
||||||
|
|
||||||
|
@ -9,6 +13,9 @@
|
||||||
Manage Subscription
|
Manage Subscription
|
||||||
</v-button>
|
</v-button>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<app-sumo-billing class="mt-4" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -16,11 +23,13 @@
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import VButton from '../../components/common/Button.vue'
|
import VButton from '../../components/common/Button.vue'
|
||||||
import SeoMeta from '../../mixins/seo-meta.js'
|
import SeoMeta from '../../mixins/seo-meta.js'
|
||||||
|
import { mapGetters } from 'vuex'
|
||||||
|
import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {VButton},
|
components: { AppSumoBilling, VButton },
|
||||||
scrollToTop: false,
|
|
||||||
mixins: [SeoMeta],
|
mixins: [SeoMeta],
|
||||||
|
scrollToTop: false,
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
metaTitle: 'Billing',
|
metaTitle: 'Billing',
|
||||||
|
@ -39,6 +48,12 @@ export default {
|
||||||
this.billingLoading = false
|
this.billingLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
user: 'auth/user'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -129,6 +129,12 @@ Route::group(['middleware' => 'guest:api'], function () {
|
||||||
Route::get('oauth/{driver}/callback', [OAuthController::class, 'handleCallback'])->name('oauth.callback');
|
Route::get('oauth/{driver}/callback', [OAuthController::class, 'handleCallback'])->name('oauth.callback');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
Route::group(['prefix' => 'appsumo'], function () {
|
||||||
|
Route::get('oauth/callback', [\App\Http\Controllers\Auth\AppSumoAuthController::class, 'handleCallback'])->name('appsumo.callback');
|
||||||
|
Route::post('webhook', [\App\Http\Controllers\Webhook\AppSumoController::class, 'handle'])->name('appsumo.webhook');
|
||||||
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Public Forms related routes
|
* Public Forms related routes
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue