mirror of
https://github.com/pixelfed/pixelfed.git
synced 2024-11-29 17:53:16 +00:00
commit
1f6dc94a34
60 changed files with 4003 additions and 296 deletions
|
@ -2,6 +2,10 @@
|
|||
|
||||
## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.11.12...dev)
|
||||
|
||||
### Features
|
||||
|
||||
- Curated Onboarding ([8dac2caf](https://github.com/pixelfed/pixelfed/commit/8dac2caf))
|
||||
|
||||
### Updates
|
||||
|
||||
- Update Inbox, cast live filters to lowercase ([d835e0ad](https://github.com/pixelfed/pixelfed/commit/d835e0ad))
|
||||
|
|
|
@ -17,269 +17,288 @@ use Illuminate\Support\Str;
|
|||
|
||||
trait AdminSettingsController
|
||||
{
|
||||
public function settings(Request $request)
|
||||
{
|
||||
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret'));
|
||||
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
|
||||
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null;
|
||||
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
|
||||
$png = in_array('image/png', $types);
|
||||
$gif = in_array('image/gif', $types);
|
||||
$mp4 = in_array('video/mp4', $types);
|
||||
$webp = in_array('image/webp', $types);
|
||||
public function settings(Request $request)
|
||||
{
|
||||
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret'));
|
||||
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
|
||||
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null;
|
||||
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
|
||||
$png = in_array('image/png', $types);
|
||||
$gif = in_array('image/gif', $types);
|
||||
$mp4 = in_array('video/mp4', $types);
|
||||
$webp = in_array('image/webp', $types);
|
||||
|
||||
$availableAdmins = User::whereIsAdmin(true)->get();
|
||||
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
|
||||
$availableAdmins = User::whereIsAdmin(true)->get();
|
||||
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
|
||||
$openReg = (bool) config_cache('pixelfed.open_registration');
|
||||
$curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
|
||||
$regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed');
|
||||
|
||||
// $system = [
|
||||
// 'permissions' => is_writable(base_path('storage')) && is_writable(base_path('bootstrap')),
|
||||
// 'max_upload_size' => ini_get('post_max_size'),
|
||||
// 'image_driver' => config('image.driver'),
|
||||
// 'image_driver_loaded' => extension_loaded(config('image.driver'))
|
||||
// ];
|
||||
return view('admin.settings.home', compact(
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'mp4',
|
||||
'webp',
|
||||
'rules',
|
||||
'cloud_storage',
|
||||
'cloud_disk',
|
||||
'cloud_ready',
|
||||
'availableAdmins',
|
||||
'currentAdmin',
|
||||
'regState'
|
||||
));
|
||||
}
|
||||
|
||||
return view('admin.settings.home', compact(
|
||||
'jpeg',
|
||||
'png',
|
||||
'gif',
|
||||
'mp4',
|
||||
'webp',
|
||||
'rules',
|
||||
'cloud_storage',
|
||||
'cloud_disk',
|
||||
'cloud_ready',
|
||||
'availableAdmins',
|
||||
'currentAdmin'
|
||||
// 'system'
|
||||
));
|
||||
}
|
||||
public function settingsHomeStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'nullable|string',
|
||||
'short_description' => 'nullable',
|
||||
'long_description' => 'nullable',
|
||||
'max_photo_size' => 'nullable|integer|min:1',
|
||||
'max_album_length' => 'nullable|integer|min:1|max:100',
|
||||
'image_quality' => 'nullable|integer|min:1|max:100',
|
||||
'type_jpeg' => 'nullable',
|
||||
'type_png' => 'nullable',
|
||||
'type_gif' => 'nullable',
|
||||
'type_mp4' => 'nullable',
|
||||
'type_webp' => 'nullable',
|
||||
'admin_account_id' => 'nullable',
|
||||
'regs' => 'required|in:open,filtered,closed'
|
||||
]);
|
||||
|
||||
public function settingsHomeStore(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'nullable|string',
|
||||
'short_description' => 'nullable',
|
||||
'long_description' => 'nullable',
|
||||
'max_photo_size' => 'nullable|integer|min:1',
|
||||
'max_album_length' => 'nullable|integer|min:1|max:100',
|
||||
'image_quality' => 'nullable|integer|min:1|max:100',
|
||||
'type_jpeg' => 'nullable',
|
||||
'type_png' => 'nullable',
|
||||
'type_gif' => 'nullable',
|
||||
'type_mp4' => 'nullable',
|
||||
'type_webp' => 'nullable',
|
||||
'admin_account_id' => 'nullable',
|
||||
]);
|
||||
$orb = false;
|
||||
$cob = false;
|
||||
switch($request->input('regs')) {
|
||||
case 'open':
|
||||
$orb = true;
|
||||
$cob = false;
|
||||
break;
|
||||
|
||||
if($request->filled('admin_account_id')) {
|
||||
ConfigCacheService::put('instance.admin.pid', $request->admin_account_id);
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
}
|
||||
if($request->filled('rule_delete')) {
|
||||
$index = (int) $request->input('rule_delete');
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$json = json_decode($rules, true);
|
||||
if(!$rules || empty($json)) {
|
||||
return;
|
||||
}
|
||||
unset($json[$index]);
|
||||
$json = json_encode(array_values($json));
|
||||
ConfigCacheService::put('app.rules', $json);
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
return 200;
|
||||
}
|
||||
case 'filtered':
|
||||
$orb = false;
|
||||
$cob = true;
|
||||
break;
|
||||
|
||||
$media_types = explode(',', config_cache('pixelfed.media_types'));
|
||||
$media_types_original = $media_types;
|
||||
case 'closed':
|
||||
$orb = false;
|
||||
$cob = false;
|
||||
break;
|
||||
}
|
||||
|
||||
$mimes = [
|
||||
'type_jpeg' => 'image/jpeg',
|
||||
'type_png' => 'image/png',
|
||||
'type_gif' => 'image/gif',
|
||||
'type_mp4' => 'video/mp4',
|
||||
'type_webp' => 'image/webp',
|
||||
];
|
||||
ConfigCacheService::put('pixelfed.open_registration', (bool) $orb);
|
||||
ConfigCacheService::put('instance.curated_registration.enabled', (bool) $cob);
|
||||
|
||||
foreach ($mimes as $key => $value) {
|
||||
if($request->input($key) == 'on') {
|
||||
if(!in_array($value, $media_types)) {
|
||||
array_push($media_types, $value);
|
||||
}
|
||||
} else {
|
||||
$media_types = array_diff($media_types, [$value]);
|
||||
}
|
||||
}
|
||||
if($request->filled('admin_account_id')) {
|
||||
ConfigCacheService::put('instance.admin.pid', $request->admin_account_id);
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
}
|
||||
if($request->filled('rule_delete')) {
|
||||
$index = (int) $request->input('rule_delete');
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$json = json_decode($rules, true);
|
||||
if(!$rules || empty($json)) {
|
||||
return;
|
||||
}
|
||||
unset($json[$index]);
|
||||
$json = json_encode(array_values($json));
|
||||
ConfigCacheService::put('app.rules', $json);
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
return 200;
|
||||
}
|
||||
|
||||
if($media_types !== $media_types_original) {
|
||||
ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types)));
|
||||
}
|
||||
$media_types = explode(',', config_cache('pixelfed.media_types'));
|
||||
$media_types_original = $media_types;
|
||||
|
||||
$keys = [
|
||||
'name' => 'app.name',
|
||||
'short_description' => 'app.short_description',
|
||||
'long_description' => 'app.description',
|
||||
'max_photo_size' => 'pixelfed.max_photo_size',
|
||||
'max_album_length' => 'pixelfed.max_album_length',
|
||||
'image_quality' => 'pixelfed.image_quality',
|
||||
'account_limit' => 'pixelfed.max_account_size',
|
||||
'custom_css' => 'uikit.custom.css',
|
||||
'custom_js' => 'uikit.custom.js',
|
||||
'about_title' => 'about.title'
|
||||
];
|
||||
$mimes = [
|
||||
'type_jpeg' => 'image/jpeg',
|
||||
'type_png' => 'image/png',
|
||||
'type_gif' => 'image/gif',
|
||||
'type_mp4' => 'video/mp4',
|
||||
'type_webp' => 'image/webp',
|
||||
];
|
||||
|
||||
foreach ($keys as $key => $value) {
|
||||
$cc = ConfigCache::whereK($value)->first();
|
||||
$val = $request->input($key);
|
||||
if($cc && $cc->v != $val) {
|
||||
ConfigCacheService::put($value, $val);
|
||||
} else if(!empty($val)) {
|
||||
ConfigCacheService::put($value, $val);
|
||||
}
|
||||
}
|
||||
foreach ($mimes as $key => $value) {
|
||||
if($request->input($key) == 'on') {
|
||||
if(!in_array($value, $media_types)) {
|
||||
array_push($media_types, $value);
|
||||
}
|
||||
} else {
|
||||
$media_types = array_diff($media_types, [$value]);
|
||||
}
|
||||
}
|
||||
|
||||
$bools = [
|
||||
'activitypub' => 'federation.activitypub.enabled',
|
||||
'open_registration' => 'pixelfed.open_registration',
|
||||
'mobile_apis' => 'pixelfed.oauth_enabled',
|
||||
'stories' => 'instance.stories.enabled',
|
||||
'ig_import' => 'pixelfed.import.instagram.enabled',
|
||||
'spam_detection' => 'pixelfed.bouncer.enabled',
|
||||
'require_email_verification' => 'pixelfed.enforce_email_verification',
|
||||
'enforce_account_limit' => 'pixelfed.enforce_account_limit',
|
||||
'show_custom_css' => 'uikit.show_custom.css',
|
||||
'show_custom_js' => 'uikit.show_custom.js',
|
||||
'cloud_storage' => 'pixelfed.cloud_storage',
|
||||
'account_autofollow' => 'account.autofollow',
|
||||
'show_directory' => 'instance.landing.show_directory',
|
||||
'show_explore_feed' => 'instance.landing.show_explore',
|
||||
];
|
||||
if($media_types !== $media_types_original) {
|
||||
ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types)));
|
||||
}
|
||||
|
||||
foreach ($bools as $key => $value) {
|
||||
$active = $request->input($key) == 'on';
|
||||
$keys = [
|
||||
'name' => 'app.name',
|
||||
'short_description' => 'app.short_description',
|
||||
'long_description' => 'app.description',
|
||||
'max_photo_size' => 'pixelfed.max_photo_size',
|
||||
'max_album_length' => 'pixelfed.max_album_length',
|
||||
'image_quality' => 'pixelfed.image_quality',
|
||||
'account_limit' => 'pixelfed.max_account_size',
|
||||
'custom_css' => 'uikit.custom.css',
|
||||
'custom_js' => 'uikit.custom.js',
|
||||
'about_title' => 'about.title'
|
||||
];
|
||||
|
||||
if($key == 'activitypub' && $active && !InstanceActor::exists()) {
|
||||
Artisan::call('instance:actor');
|
||||
}
|
||||
foreach ($keys as $key => $value) {
|
||||
$cc = ConfigCache::whereK($value)->first();
|
||||
$val = $request->input($key);
|
||||
if($cc && $cc->v != $val) {
|
||||
ConfigCacheService::put($value, $val);
|
||||
} else if(!empty($val)) {
|
||||
ConfigCacheService::put($value, $val);
|
||||
}
|
||||
}
|
||||
|
||||
if( $key == 'mobile_apis' &&
|
||||
$active &&
|
||||
!file_exists(storage_path('oauth-public.key')) &&
|
||||
!file_exists(storage_path('oauth-private.key'))
|
||||
) {
|
||||
Artisan::call('passport:keys');
|
||||
Artisan::call('route:cache');
|
||||
}
|
||||
$bools = [
|
||||
'activitypub' => 'federation.activitypub.enabled',
|
||||
// 'open_registration' => 'pixelfed.open_registration',
|
||||
'mobile_apis' => 'pixelfed.oauth_enabled',
|
||||
'stories' => 'instance.stories.enabled',
|
||||
'ig_import' => 'pixelfed.import.instagram.enabled',
|
||||
'spam_detection' => 'pixelfed.bouncer.enabled',
|
||||
'require_email_verification' => 'pixelfed.enforce_email_verification',
|
||||
'enforce_account_limit' => 'pixelfed.enforce_account_limit',
|
||||
'show_custom_css' => 'uikit.show_custom.css',
|
||||
'show_custom_js' => 'uikit.show_custom.js',
|
||||
'cloud_storage' => 'pixelfed.cloud_storage',
|
||||
'account_autofollow' => 'account.autofollow',
|
||||
'show_directory' => 'instance.landing.show_directory',
|
||||
'show_explore_feed' => 'instance.landing.show_explore',
|
||||
];
|
||||
|
||||
if(config_cache($value) !== $active) {
|
||||
ConfigCacheService::put($value, (bool) $active);
|
||||
}
|
||||
}
|
||||
foreach ($bools as $key => $value) {
|
||||
$active = $request->input($key) == 'on';
|
||||
|
||||
if($request->filled('new_rule')) {
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('new_rule');
|
||||
if(!$rules) {
|
||||
ConfigCacheService::put('app.rules', json_encode([$val]));
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$json[] = $val;
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
}
|
||||
if($key == 'activitypub' && $active && !InstanceActor::exists()) {
|
||||
Artisan::call('instance:actor');
|
||||
}
|
||||
|
||||
if($request->filled('account_autofollow_usernames')) {
|
||||
$usernames = explode(',', $request->input('account_autofollow_usernames'));
|
||||
$names = [];
|
||||
if( $key == 'mobile_apis' &&
|
||||
$active &&
|
||||
!file_exists(storage_path('oauth-public.key')) &&
|
||||
!file_exists(storage_path('oauth-private.key'))
|
||||
) {
|
||||
Artisan::call('passport:keys');
|
||||
Artisan::call('route:cache');
|
||||
}
|
||||
|
||||
foreach($usernames as $n) {
|
||||
$p = Profile::whereUsername($n)->first();
|
||||
if(!$p) {
|
||||
continue;
|
||||
}
|
||||
array_push($names, $p->username);
|
||||
}
|
||||
if(config_cache($value) !== $active) {
|
||||
ConfigCacheService::put($value, (bool) $active);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
}
|
||||
if($request->filled('new_rule')) {
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('new_rule');
|
||||
if(!$rules) {
|
||||
ConfigCacheService::put('app.rules', json_encode([$val]));
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$json[] = $val;
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
}
|
||||
|
||||
Cache::forget(Config::CACHE_KEY);
|
||||
if($request->filled('account_autofollow_usernames')) {
|
||||
$usernames = explode(',', $request->input('account_autofollow_usernames'));
|
||||
$names = [];
|
||||
|
||||
return redirect('/i/admin/settings')->with('status', 'Successfully updated settings!');
|
||||
}
|
||||
foreach($usernames as $n) {
|
||||
$p = Profile::whereUsername($n)->first();
|
||||
if(!$p) {
|
||||
continue;
|
||||
}
|
||||
array_push($names, $p->username);
|
||||
}
|
||||
|
||||
public function settingsBackups(Request $request)
|
||||
{
|
||||
$path = storage_path('app/'.config('app.name'));
|
||||
$files = is_dir($path) ? new \DirectoryIterator($path) : [];
|
||||
return view('admin.settings.backups', compact('files'));
|
||||
}
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
}
|
||||
|
||||
public function settingsMaintenance(Request $request)
|
||||
{
|
||||
return view('admin.settings.maintenance');
|
||||
}
|
||||
Cache::forget(Config::CACHE_KEY);
|
||||
|
||||
public function settingsStorage(Request $request)
|
||||
{
|
||||
$storage = [];
|
||||
return view('admin.settings.storage', compact('storage'));
|
||||
}
|
||||
return redirect('/i/admin/settings')->with('status', 'Successfully updated settings!');
|
||||
}
|
||||
|
||||
public function settingsFeatures(Request $request)
|
||||
{
|
||||
return view('admin.settings.features');
|
||||
}
|
||||
public function settingsBackups(Request $request)
|
||||
{
|
||||
$path = storage_path('app/'.config('app.name'));
|
||||
$files = is_dir($path) ? new \DirectoryIterator($path) : [];
|
||||
return view('admin.settings.backups', compact('files'));
|
||||
}
|
||||
|
||||
public function settingsPages(Request $request)
|
||||
{
|
||||
$pages = Page::orderByDesc('updated_at')->paginate(10);
|
||||
return view('admin.pages.home', compact('pages'));
|
||||
}
|
||||
public function settingsMaintenance(Request $request)
|
||||
{
|
||||
return view('admin.settings.maintenance');
|
||||
}
|
||||
|
||||
public function settingsPageEdit(Request $request)
|
||||
{
|
||||
return view('admin.pages.edit');
|
||||
}
|
||||
public function settingsStorage(Request $request)
|
||||
{
|
||||
$storage = [];
|
||||
return view('admin.settings.storage', compact('storage'));
|
||||
}
|
||||
|
||||
public function settingsSystem(Request $request)
|
||||
{
|
||||
$sys = [
|
||||
'pixelfed' => config('pixelfed.version'),
|
||||
'php' => phpversion(),
|
||||
'laravel' => app()->version(),
|
||||
];
|
||||
switch (config('database.default')) {
|
||||
case 'pgsql':
|
||||
$exp = DB::raw('select version();');
|
||||
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
|
||||
$sys['database'] = [
|
||||
'name' => 'Postgres',
|
||||
'version' => explode(' ', DB::select($expQuery)[0]->version)[1]
|
||||
];
|
||||
break;
|
||||
public function settingsFeatures(Request $request)
|
||||
{
|
||||
return view('admin.settings.features');
|
||||
}
|
||||
|
||||
case 'mysql':
|
||||
$exp = DB::raw('select version()');
|
||||
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
|
||||
$sys['database'] = [
|
||||
'name' => 'MySQL',
|
||||
'version' => DB::select($expQuery)[0]->{'version()'}
|
||||
];
|
||||
break;
|
||||
public function settingsPages(Request $request)
|
||||
{
|
||||
$pages = Page::orderByDesc('updated_at')->paginate(10);
|
||||
return view('admin.pages.home', compact('pages'));
|
||||
}
|
||||
|
||||
default:
|
||||
$sys['database'] = [
|
||||
'name' => 'Unknown',
|
||||
'version' => '?'
|
||||
];
|
||||
break;
|
||||
}
|
||||
return view('admin.settings.system', compact('sys'));
|
||||
}
|
||||
public function settingsPageEdit(Request $request)
|
||||
{
|
||||
return view('admin.pages.edit');
|
||||
}
|
||||
|
||||
public function settingsSystem(Request $request)
|
||||
{
|
||||
$sys = [
|
||||
'pixelfed' => config('pixelfed.version'),
|
||||
'php' => phpversion(),
|
||||
'laravel' => app()->version(),
|
||||
];
|
||||
switch (config('database.default')) {
|
||||
case 'pgsql':
|
||||
$exp = DB::raw('select version();');
|
||||
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
|
||||
$sys['database'] = [
|
||||
'name' => 'Postgres',
|
||||
'version' => explode(' ', DB::select($expQuery)[0]->version)[1]
|
||||
];
|
||||
break;
|
||||
|
||||
case 'mysql':
|
||||
$exp = DB::raw('select version()');
|
||||
$expQuery = $exp->getValue(DB::connection()->getQueryGrammar());
|
||||
$sys['database'] = [
|
||||
'name' => 'MySQL',
|
||||
'version' => DB::select($expQuery)[0]->{'version()'}
|
||||
];
|
||||
break;
|
||||
|
||||
default:
|
||||
$sys['database'] = [
|
||||
'name' => 'Unknown',
|
||||
'version' => '?'
|
||||
];
|
||||
break;
|
||||
}
|
||||
return view('admin.settings.system', compact('sys'));
|
||||
}
|
||||
}
|
||||
|
|
226
app/Http/Controllers/AdminCuratedRegisterController.php
Normal file
226
app/Http/Controllers/AdminCuratedRegisterController.php
Normal file
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\CuratedRegister;
|
||||
use App\Models\CuratedRegisterActivity;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Mail\CuratedRegisterRequestDetailsFromUser;
|
||||
use App\Mail\CuratedRegisterAcceptUser;
|
||||
use App\Mail\CuratedRegisterRejectUser;
|
||||
use App\User;
|
||||
|
||||
class AdminCuratedRegisterController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(['auth','admin']);
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'filter' => 'sometimes|in:open,all,awaiting,approved,rejected'
|
||||
]);
|
||||
$filter = $request->input('filter', 'open');
|
||||
$records = CuratedRegister::when($filter, function($q, $filter) {
|
||||
if($filter === 'open') {
|
||||
return $q->where('is_rejected', false)
|
||||
->whereNotNull('email_verified_at')
|
||||
->whereIsClosed(false);
|
||||
} else if($filter === 'all') {
|
||||
return $q;
|
||||
} elseif ($filter === 'awaiting') {
|
||||
return $q->whereIsClosed(false)
|
||||
->whereNull('is_rejected')
|
||||
->whereNull('is_approved');
|
||||
} elseif ($filter === 'approved') {
|
||||
return $q->whereIsClosed(true)->whereIsApproved(true);
|
||||
} elseif ($filter === 'rejected') {
|
||||
return $q->whereIsClosed(true)->whereIsRejected(true);
|
||||
}
|
||||
})
|
||||
->latest()
|
||||
->paginate(10);
|
||||
return view('admin.curated-register.index', compact('records', 'filter'));
|
||||
}
|
||||
|
||||
public function show(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
return view('admin.curated-register.show', compact('record'));
|
||||
}
|
||||
|
||||
public function apiActivityLog(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
|
||||
$res = collect([
|
||||
[
|
||||
'id' => 1,
|
||||
'action' => 'created',
|
||||
'title' => 'Onboarding application created',
|
||||
'message' => null,
|
||||
'link' => null,
|
||||
'timestamp' => $record->created_at,
|
||||
]
|
||||
]);
|
||||
|
||||
if($record->email_verified_at) {
|
||||
$res->push([
|
||||
'id' => 3,
|
||||
'action' => 'email_verified_at',
|
||||
'title' => 'Applicant successfully verified email address',
|
||||
'message' => null,
|
||||
'link' => null,
|
||||
'timestamp' => $record->email_verified_at,
|
||||
]);
|
||||
}
|
||||
|
||||
$activities = CuratedRegisterActivity::whereRegisterId($record->id)->get();
|
||||
|
||||
$idx = 4;
|
||||
$userResponses = collect([]);
|
||||
|
||||
foreach($activities as $activity) {
|
||||
$idx++;
|
||||
if($activity->from_user) {
|
||||
$userResponses->push($activity);
|
||||
continue;
|
||||
}
|
||||
$res->push([
|
||||
'id' => $idx,
|
||||
'aid' => $activity->id,
|
||||
'action' => $activity->type,
|
||||
'title' => $activity->from_admin ? 'Admin requested info' : 'User responded',
|
||||
'message' => $activity->message,
|
||||
'link' => $activity->adminReviewUrl(),
|
||||
'timestamp' => $activity->created_at,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach($userResponses as $ur) {
|
||||
$res = $res->map(function($r) use($ur) {
|
||||
if(!isset($r['aid'])) {
|
||||
return $r;
|
||||
}
|
||||
if($ur->reply_to_id === $r['aid']) {
|
||||
$r['user_response'] = $ur;
|
||||
return $r;
|
||||
}
|
||||
return $r;
|
||||
});
|
||||
}
|
||||
|
||||
if($record->is_approved) {
|
||||
$idx++;
|
||||
$res->push([
|
||||
'id' => $idx,
|
||||
'action' => 'approved',
|
||||
'title' => 'Application Approved',
|
||||
'message' => null,
|
||||
'link' => null,
|
||||
'timestamp' => $record->action_taken_at,
|
||||
]);
|
||||
} else if ($record->is_rejected) {
|
||||
$idx++;
|
||||
$res->push([
|
||||
'id' => $idx,
|
||||
'action' => 'rejected',
|
||||
'title' => 'Application Rejected',
|
||||
'message' => null,
|
||||
'link' => null,
|
||||
'timestamp' => $record->action_taken_at,
|
||||
]);
|
||||
}
|
||||
|
||||
return $res->reverse()->values();
|
||||
}
|
||||
|
||||
public function apiMessagePreviewStore(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function apiMessageSendStore(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'message' => 'required|string|min:5|max:1000'
|
||||
]);
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
|
||||
$activity = new CuratedRegisterActivity;
|
||||
$activity->register_id = $record->id;
|
||||
$activity->admin_id = $request->user()->id;
|
||||
$activity->secret_code = Str::random(32);
|
||||
$activity->type = 'request_details';
|
||||
$activity->from_admin = true;
|
||||
$activity->message = $request->input('message');
|
||||
$activity->save();
|
||||
$record->is_awaiting_more_info = true;
|
||||
$record->save();
|
||||
Mail::to($record->email)->send(new CuratedRegisterRequestDetailsFromUser($record, $activity));
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function previewDetailsMessageShow(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
|
||||
$activity = new CuratedRegisterActivity;
|
||||
$activity->message = $request->input('message');
|
||||
return new \App\Mail\CuratedRegisterRequestDetailsFromUser($record, $activity);
|
||||
}
|
||||
|
||||
|
||||
public function previewMessageShow(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
abort_if($record->email_verified_at === null, 400, 'Cannot message an unverified email');
|
||||
$record->message = $request->input('message');
|
||||
return new \App\Mail\CuratedRegisterSendMessage($record);
|
||||
}
|
||||
|
||||
public function apiHandleReject(Request $request, $id)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'action' => 'required|in:reject-email,reject-silent'
|
||||
]);
|
||||
$action = $request->input('action');
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
abort_if($record->email_verified_at === null, 400, 'Cannot reject an unverified email');
|
||||
$record->is_rejected = true;
|
||||
$record->is_closed = true;
|
||||
$record->action_taken_at = now();
|
||||
$record->save();
|
||||
if($action === 'reject-email') {
|
||||
Mail::to($record->email)->send(new CuratedRegisterRejectUser($record));
|
||||
}
|
||||
return [200];
|
||||
}
|
||||
|
||||
public function apiHandleApprove(Request $request, $id)
|
||||
{
|
||||
$record = CuratedRegister::findOrFail($id);
|
||||
abort_if($record->email_verified_at === null, 400, 'Cannot reject an unverified email');
|
||||
$record->is_approved = true;
|
||||
$record->is_closed = true;
|
||||
$record->action_taken_at = now();
|
||||
$record->save();
|
||||
$user = User::create([
|
||||
'name' => $record->username,
|
||||
'username' => $record->username,
|
||||
'email' => $record->email,
|
||||
'password' => $record->password,
|
||||
'app_register_ip' => $record->ip_address,
|
||||
'email_verified_at' => now(),
|
||||
'register_source' => 'cur_onboarding'
|
||||
]);
|
||||
|
||||
Mail::to($record->email)->send(new CuratedRegisterAcceptUser($record));
|
||||
return [200];
|
||||
}
|
||||
}
|
|
@ -1652,13 +1652,13 @@ class ApiV1Controller extends Controller
|
|||
'email' => config('instance.email'),
|
||||
'version' => '3.5.3 (compatible; Pixelfed ' . config('pixelfed.version') .')',
|
||||
'urls' => [
|
||||
'streaming_api' => 'wss://' . config('pixelfed.domain.app')
|
||||
'streaming_api' => null,
|
||||
],
|
||||
'stats' => $stats,
|
||||
'thumbnail' => config_cache('app.banner_image') ?? url(Storage::url('public/headers/default.jpg')),
|
||||
'languages' => [config('app.locale')],
|
||||
'registrations' => (bool) config_cache('pixelfed.open_registration'),
|
||||
'approval_required' => false, // (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'approval_required' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'contact_account' => $contact,
|
||||
'rules' => $rules,
|
||||
'configuration' => [
|
||||
|
|
|
@ -141,6 +141,7 @@ class ApiV2Controller extends Controller
|
|||
});
|
||||
|
||||
$res['registrations']['enabled'] = (bool) config_cache('pixelfed.open_registration');
|
||||
$res['registrations']['approval_required'] = (bool) config_cache('instance.curated_registration.enabled');
|
||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,7 @@ class RegisterController extends Controller
|
|||
*/
|
||||
public function showRegistrationForm()
|
||||
{
|
||||
if(config_cache('pixelfed.open_registration')) {
|
||||
if((bool) config_cache('pixelfed.open_registration')) {
|
||||
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
|
||||
abort_if(BouncerService::checkIp(request()->ip()), 404);
|
||||
}
|
||||
|
@ -191,7 +191,11 @@ class RegisterController extends Controller
|
|||
return view('auth.register');
|
||||
}
|
||||
} else {
|
||||
abort(404);
|
||||
if((bool) config_cache('instance.curated_registration.enabled') && config('instance.curated_registration.state.fallback_on_closed_reg')) {
|
||||
return redirect('/auth/sign_up');
|
||||
} else {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
398
app/Http/Controllers/CuratedRegisterController.php
Normal file
398
app/Http/Controllers/CuratedRegisterController.php
Normal file
|
@ -0,0 +1,398 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\User;
|
||||
use App\Models\CuratedRegister;
|
||||
use App\Models\CuratedRegisterActivity;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\BouncerService;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use App\Mail\CuratedRegisterConfirmEmail;
|
||||
use App\Mail\CuratedRegisterNotifyAdmin;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use App\Jobs\CuratedOnboarding\CuratedOnboardingNotifyAdminNewApplicationPipeline;
|
||||
|
||||
class CuratedRegisterController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
abort_unless((bool) config_cache('instance.curated_registration.enabled'), 404);
|
||||
|
||||
if((bool) config_cache('pixelfed.open_registration')) {
|
||||
abort_if(config('instance.curated_registration.state.only_enabled_on_closed_reg'), 404);
|
||||
} else {
|
||||
abort_unless(config('instance.curated_registration.state.fallback_on_closed_reg'), 404);
|
||||
}
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
abort_if($request->user(), 404);
|
||||
return view('auth.curated-register.index', ['step' => 1]);
|
||||
}
|
||||
|
||||
public function concierge(Request $request)
|
||||
{
|
||||
abort_if($request->user(), 404);
|
||||
$emailConfirmed = $request->session()->has('cur-reg-con.email-confirmed') &&
|
||||
$request->has('next') &&
|
||||
$request->session()->has('cur-reg-con.cr-id');
|
||||
return view('auth.curated-register.concierge', compact('emailConfirmed'));
|
||||
}
|
||||
|
||||
public function conciergeResponseSent(Request $request)
|
||||
{
|
||||
return view('auth.curated-register.user_response_sent');
|
||||
}
|
||||
|
||||
public function conciergeFormShow(Request $request)
|
||||
{
|
||||
abort_if($request->user(), 404);
|
||||
abort_unless(
|
||||
$request->session()->has('cur-reg-con.email-confirmed') &&
|
||||
$request->session()->has('cur-reg-con.cr-id') &&
|
||||
$request->session()->has('cur-reg-con.ac-id'), 404);
|
||||
$crid = $request->session()->get('cur-reg-con.cr-id');
|
||||
$arid = $request->session()->get('cur-reg-con.ac-id');
|
||||
$showCaptcha = config('instance.curated_registration.captcha_enabled');
|
||||
if($attempts = $request->session()->get('cur-reg-con-attempt')) {
|
||||
$showCaptcha = $attempts && $attempts >= 2;
|
||||
} else {
|
||||
$showCaptcha = false;
|
||||
}
|
||||
$activity = CuratedRegisterActivity::whereRegisterId($crid)->whereFromAdmin(true)->findOrFail($arid);
|
||||
return view('auth.curated-register.concierge_form', compact('activity', 'showCaptcha'));
|
||||
}
|
||||
|
||||
public function conciergeFormStore(Request $request)
|
||||
{
|
||||
abort_if($request->user(), 404);
|
||||
$request->session()->increment('cur-reg-con-attempt');
|
||||
abort_unless(
|
||||
$request->session()->has('cur-reg-con.email-confirmed') &&
|
||||
$request->session()->has('cur-reg-con.cr-id') &&
|
||||
$request->session()->has('cur-reg-con.ac-id'), 404);
|
||||
$attempts = $request->session()->get('cur-reg-con-attempt');
|
||||
$messages = [];
|
||||
$rules = [
|
||||
'response' => 'required|string|min:5|max:1000',
|
||||
'crid' => 'required|integer|min:1',
|
||||
'acid' => 'required|integer|min:1'
|
||||
];
|
||||
if(config('instance.curated_registration.captcha_enabled') && $attempts >= 3) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'The captcha must be filled';
|
||||
}
|
||||
$this->validate($request, $rules, $messages);
|
||||
$crid = $request->session()->get('cur-reg-con.cr-id');
|
||||
$acid = $request->session()->get('cur-reg-con.ac-id');
|
||||
abort_if((string) $crid !== $request->input('crid'), 404);
|
||||
abort_if((string) $acid !== $request->input('acid'), 404);
|
||||
|
||||
if(CuratedRegisterActivity::whereRegisterId($crid)->whereReplyToId($acid)->exists()) {
|
||||
return redirect()->back()->withErrors(['code' => 'You already replied to this request.']);
|
||||
}
|
||||
|
||||
$act = CuratedRegisterActivity::create([
|
||||
'register_id' => $crid,
|
||||
'reply_to_id' => $acid,
|
||||
'type' => 'user_response',
|
||||
'message' => $request->input('response'),
|
||||
'from_user' => true,
|
||||
'action_required' => true,
|
||||
]);
|
||||
|
||||
$request->session()->pull('cur-reg-con');
|
||||
$request->session()->pull('cur-reg-con-attempt');
|
||||
|
||||
return view('auth.curated-register.user_response_sent');
|
||||
}
|
||||
|
||||
public function conciergeStore(Request $request)
|
||||
{
|
||||
abort_if($request->user(), 404);
|
||||
$rules = [
|
||||
'sid' => 'required_if:action,email|integer|min:1|max:20000000',
|
||||
'id' => 'required_if:action,email|integer|min:1|max:20000000',
|
||||
'code' => 'required_if:action,email',
|
||||
'action' => 'required|string|in:email,message',
|
||||
'email' => 'required_if:action,email|email',
|
||||
'response' => 'required_if:action,message|string|min:20|max:1000',
|
||||
];
|
||||
$messages = [];
|
||||
if(config('instance.curated_registration.captcha_enabled')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'The captcha must be filled';
|
||||
}
|
||||
$this->validate($request, $rules, $messages);
|
||||
|
||||
$action = $request->input('action');
|
||||
$sid = $request->input('sid');
|
||||
$id = $request->input('id');
|
||||
$code = $request->input('code');
|
||||
$email = $request->input('email');
|
||||
|
||||
$cr = CuratedRegister::whereIsClosed(false)->findOrFail($sid);
|
||||
$ac = CuratedRegisterActivity::whereRegisterId($cr->id)->whereFromAdmin(true)->findOrFail($id);
|
||||
|
||||
if(!hash_equals($ac->secret_code, $code)) {
|
||||
return redirect()->back()->withErrors(['code' => 'Invalid code']);
|
||||
}
|
||||
|
||||
if(!hash_equals($cr->email, $email)) {
|
||||
return redirect()->back()->withErrors(['email' => 'Invalid email']);
|
||||
}
|
||||
|
||||
$request->session()->put('cur-reg-con.email-confirmed', true);
|
||||
$request->session()->put('cur-reg-con.cr-id', $cr->id);
|
||||
$request->session()->put('cur-reg-con.ac-id', $ac->id);
|
||||
$emailConfirmed = true;
|
||||
return redirect('/auth/sign_up/concierge/form');
|
||||
}
|
||||
|
||||
public function confirmEmail(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect(route('help.email-confirmation-issues'));
|
||||
}
|
||||
return view('auth.curated-register.confirm_email');
|
||||
}
|
||||
|
||||
public function emailConfirmed(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect(route('help.email-confirmation-issues'));
|
||||
}
|
||||
return view('auth.curated-register.email_confirmed');
|
||||
}
|
||||
|
||||
public function resendConfirmation(Request $request)
|
||||
{
|
||||
return view('auth.curated-register.resend-confirmation');
|
||||
}
|
||||
|
||||
public function resendConfirmationProcess(Request $request)
|
||||
{
|
||||
$rules = [
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
|
||||
'exists:curated_registers',
|
||||
]
|
||||
];
|
||||
|
||||
$messages = [];
|
||||
|
||||
if(config('instance.curated_registration.captcha_enabled')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'The captcha must be filled';
|
||||
}
|
||||
|
||||
$this->validate($request, $rules, $messages);
|
||||
|
||||
$cur = CuratedRegister::whereEmail($request->input('email'))->whereIsClosed(false)->first();
|
||||
if(!$cur) {
|
||||
return redirect()->back()->withErrors(['email' => 'The selected email is invalid.']);
|
||||
}
|
||||
|
||||
$totalCount = CuratedRegisterActivity::whereRegisterId($cur->id)
|
||||
->whereType('user_resend_email_confirmation')
|
||||
->count();
|
||||
|
||||
if($totalCount && $totalCount >= config('instance.curated_registration.resend_confirmation_limit')) {
|
||||
return redirect()->back()->withErrors(['email' => 'You have re-attempted too many times. To proceed with your application, please <a href="/site/contact" class="text-white" style="text-decoration: underline;">contact the admin team</a>.']);
|
||||
}
|
||||
|
||||
$count = CuratedRegisterActivity::whereRegisterId($cur->id)
|
||||
->whereType('user_resend_email_confirmation')
|
||||
->where('created_at', '>', now()->subHours(12))
|
||||
->count();
|
||||
|
||||
if($count) {
|
||||
return redirect()->back()->withErrors(['email' => 'You can only re-send the confirmation email once per 12 hours. Try again later.']);
|
||||
}
|
||||
|
||||
CuratedRegisterActivity::create([
|
||||
'register_id' => $cur->id,
|
||||
'type' => 'user_resend_email_confirmation',
|
||||
'admin_only_view' => true,
|
||||
'from_admin' => false,
|
||||
'from_user' => false,
|
||||
'action_required' => false,
|
||||
]);
|
||||
|
||||
Mail::to($cur->email)->send(new CuratedRegisterConfirmEmail($cur));
|
||||
return view('auth.curated-register.resent-confirmation');
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function confirmEmailHandle(Request $request)
|
||||
{
|
||||
$rules = [
|
||||
'sid' => 'required',
|
||||
'code' => 'required'
|
||||
];
|
||||
$messages = [];
|
||||
if(config('instance.curated_registration.captcha_enabled')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'The captcha must be filled';
|
||||
}
|
||||
$this->validate($request, $rules, $messages);
|
||||
|
||||
$cr = CuratedRegister::whereNull('email_verified_at')
|
||||
->where('created_at', '>', now()->subHours(24))
|
||||
->find($request->input('sid'));
|
||||
if(!$cr) {
|
||||
return redirect(route('help.email-confirmation-issues'));
|
||||
}
|
||||
if(!hash_equals($cr->verify_code, $request->input('code'))) {
|
||||
return redirect(route('help.email-confirmation-issues'));
|
||||
}
|
||||
$cr->email_verified_at = now();
|
||||
$cr->save();
|
||||
|
||||
if(config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
|
||||
CuratedOnboardingNotifyAdminNewApplicationPipeline::dispatch($cr);
|
||||
}
|
||||
return view('auth.curated-register.email_confirmed');
|
||||
}
|
||||
|
||||
public function proceed(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'step' => 'required|integer|in:1,2,3,4'
|
||||
]);
|
||||
$step = $request->input('step');
|
||||
|
||||
switch($step) {
|
||||
case 1:
|
||||
$step = 2;
|
||||
$request->session()->put('cur-step', 1);
|
||||
return view('auth.curated-register.index', compact('step'));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$this->stepTwo($request);
|
||||
$step = 3;
|
||||
$request->session()->put('cur-step', 2);
|
||||
return view('auth.curated-register.index', compact('step'));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$this->stepThree($request);
|
||||
$step = 3;
|
||||
$request->session()->put('cur-step', 3);
|
||||
$verifiedEmail = true;
|
||||
$request->session()->pull('cur-reg');
|
||||
return view('auth.curated-register.index', compact('step', 'verifiedEmail'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function stepTwo($request)
|
||||
{
|
||||
if($request->filled('reason')) {
|
||||
$request->session()->put('cur-reg.form-reason', $request->input('reason'));
|
||||
}
|
||||
if($request->filled('username')) {
|
||||
$request->session()->put('cur-reg.form-username', $request->input('username'));
|
||||
}
|
||||
if($request->filled('email')) {
|
||||
$request->session()->put('cur-reg.form-email', $request->input('email'));
|
||||
}
|
||||
$this->validate($request, [
|
||||
'username' => [
|
||||
'required',
|
||||
'min:2',
|
||||
'max:15',
|
||||
'unique:curated_registers',
|
||||
'unique:users',
|
||||
function ($attribute, $value, $fail) {
|
||||
$dash = substr_count($value, '-');
|
||||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
return $fail('Username is invalid. Must end with a letter or number.');
|
||||
}
|
||||
|
||||
$val = str_replace(['_', '.', '-'], '', $value);
|
||||
if(!ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
$restricted = RestrictedNames::get();
|
||||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
|
||||
'max:255',
|
||||
'unique:users',
|
||||
'unique:curated_registers',
|
||||
function ($attribute, $value, $fail) {
|
||||
$banned = EmailService::isBanned($value);
|
||||
if($banned) {
|
||||
return $fail('Email is invalid.');
|
||||
}
|
||||
},
|
||||
],
|
||||
'password' => 'required|min:8',
|
||||
'password_confirmation' => 'required|same:password',
|
||||
'reason' => 'required|min:20|max:1000',
|
||||
'agree' => 'required|accepted'
|
||||
]);
|
||||
$request->session()->put('cur-reg.form-email', $request->input('email'));
|
||||
$request->session()->put('cur-reg.form-password', $request->input('password'));
|
||||
}
|
||||
|
||||
protected function stepThree($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'email' => [
|
||||
'required',
|
||||
'string',
|
||||
app()->environment() === 'production' ? 'email:rfc,dns,spoof' : 'email',
|
||||
'max:255',
|
||||
'unique:users',
|
||||
'unique:curated_registers',
|
||||
function ($attribute, $value, $fail) {
|
||||
$banned = EmailService::isBanned($value);
|
||||
if($banned) {
|
||||
return $fail('Email is invalid.');
|
||||
}
|
||||
},
|
||||
]
|
||||
]);
|
||||
$cr = new CuratedRegister;
|
||||
$cr->email = $request->email;
|
||||
$cr->username = $request->session()->get('cur-reg.form-username');
|
||||
$cr->password = bcrypt($request->session()->get('cur-reg.form-password'));
|
||||
$cr->ip_address = $request->ip();
|
||||
$cr->reason_to_join = $request->session()->get('cur-reg.form-reason');
|
||||
$cr->verify_code = Str::random(40);
|
||||
$cr->save();
|
||||
|
||||
Mail::to($cr->email)->send(new CuratedRegisterConfirmEmail($cr));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs\CuratedOnboarding;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\CuratedRegister;
|
||||
use App\User;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Mail\CuratedRegisterNotifyAdmin;
|
||||
|
||||
class CuratedOnboardingNotifyAdminNewApplicationPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $cr;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(CuratedRegister $cr)
|
||||
{
|
||||
$this->cr = $cr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
if(!config('instance.curated_registration.notify.admin.on_verify_email.enabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
config('instance.curated_registration.notify.admin.on_verify_email.bundle') ?
|
||||
$this->handleBundled() :
|
||||
$this->handleUnbundled();
|
||||
}
|
||||
|
||||
protected function handleBundled()
|
||||
{
|
||||
$cr = $this->cr;
|
||||
Storage::append('conanap.json', json_encode([
|
||||
'id' => $cr->id,
|
||||
'email' => $cr->email,
|
||||
'created_at' => $cr->created_at,
|
||||
'updated_at' => $cr->updated_at,
|
||||
]));
|
||||
}
|
||||
|
||||
protected function handleUnbundled()
|
||||
{
|
||||
$cr = $this->cr;
|
||||
if($aid = config_cache('instance.admin.pid')) {
|
||||
$admin = User::whereProfileId($aid)->first();
|
||||
if($admin && $admin->email) {
|
||||
Mail::to($admin->email)->send(new CuratedRegisterNotifyAdmin($cr));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterAcceptUser.php
Normal file
55
app/Mail/CuratedRegisterAcceptUser.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CuratedRegisterAcceptUser extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $verify;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct($verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.request-accepted',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterConfirmEmail.php
Normal file
55
app/Mail/CuratedRegisterConfirmEmail.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\CuratedRegister;
|
||||
|
||||
class CuratedRegisterConfirmEmail extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
public $verify;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(CuratedRegister $verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Welcome to Pixelfed! Please Confirm Your Email',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.confirm_email',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterNotifyAdmin.php
Normal file
55
app/Mail/CuratedRegisterNotifyAdmin.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\CuratedRegister;
|
||||
|
||||
class CuratedRegisterNotifyAdmin extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
public $verify;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(CuratedRegister $verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '[Requires Action]: New Curated Onboarding Application',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.admin_notify',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterNotifyAdminUserResponse.php
Normal file
55
app/Mail/CuratedRegisterNotifyAdminUserResponse.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CuratedRegisterNotifyAdminUserResponse extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Curated Register Notify Admin User Response',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.admin_notify_user_response',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterRejectUser.php
Normal file
55
app/Mail/CuratedRegisterRejectUser.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CuratedRegisterRejectUser extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $verify;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct($verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.request-rejected',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
58
app/Mail/CuratedRegisterRequestDetailsFromUser.php
Normal file
58
app/Mail/CuratedRegisterRequestDetailsFromUser.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Models\CuratedRegister;
|
||||
use App\Models\CuratedRegisterActivity;
|
||||
|
||||
class CuratedRegisterRequestDetailsFromUser extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
public $verify;
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct(CuratedRegister $verify, CuratedRegisterActivity $activity)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: '[Action Needed]: Additional information requested',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.request-details-from-user',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
55
app/Mail/CuratedRegisterSendMessage.php
Normal file
55
app/Mail/CuratedRegisterSendMessage.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Mail\Mailables\Content;
|
||||
use Illuminate\Mail\Mailables\Envelope;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class CuratedRegisterSendMessage extends Mailable
|
||||
{
|
||||
use Queueable, SerializesModels;
|
||||
|
||||
public $verify;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*/
|
||||
public function __construct($verify)
|
||||
{
|
||||
$this->verify = $verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message envelope.
|
||||
*/
|
||||
public function envelope(): Envelope
|
||||
{
|
||||
return new Envelope(
|
||||
subject: 'Your ' . config('pixelfed.domain.app') . ' Registration Update',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message content definition.
|
||||
*/
|
||||
public function content(): Content
|
||||
{
|
||||
return new Content(
|
||||
markdown: 'emails.curated-register.message-from-admin',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attachments for the message.
|
||||
*
|
||||
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
|
||||
*/
|
||||
public function attachments(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
49
app/Models/CuratedRegister.php
Normal file
49
app/Models/CuratedRegister.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CuratedRegister extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $casts = [
|
||||
'autofollow_account_ids' => 'array',
|
||||
'admin_notes' => 'array',
|
||||
'email_verified_at' => 'datetime',
|
||||
'admin_notified_at' => 'datetime',
|
||||
'action_taken_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function adminStatusLabel()
|
||||
{
|
||||
if(!$this->email_verified_at) {
|
||||
return '<span class="border border-danger px-3 py-1 rounded text-white font-weight-bold">Unverified email</span>';
|
||||
}
|
||||
if($this->is_accepted) { return 'Approved'; }
|
||||
if($this->is_rejected) { return 'Rejected'; }
|
||||
if($this->is_awaiting_more_info ) {
|
||||
return '<span class="border border-info px-3 py-1 rounded text-white font-weight-bold">Awaiting Details</span>';
|
||||
}
|
||||
if($this->is_closed ) { return 'Closed'; }
|
||||
|
||||
return '<span class="border border-success px-3 py-1 rounded text-white font-weight-bold">Open</span>';
|
||||
}
|
||||
|
||||
public function emailConfirmUrl()
|
||||
{
|
||||
return url('/auth/sign_up/confirm?sid=' . $this->id . '&code=' . $this->verify_code);
|
||||
}
|
||||
|
||||
public function emailReplyUrl()
|
||||
{
|
||||
return url('/auth/sign_up/concierge?sid=' . $this->id . '&code=' . $this->verify_code . '&sc=' . str_random(8));
|
||||
}
|
||||
|
||||
public function adminReviewUrl()
|
||||
{
|
||||
return url('/i/admin/curated-onboarding/show/' . $this->id);
|
||||
}
|
||||
}
|
38
app/Models/CuratedRegisterActivity.php
Normal file
38
app/Models/CuratedRegisterActivity.php
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CuratedRegisterActivity extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'metadata' => 'array',
|
||||
'admin_notified_at' => 'datetime',
|
||||
'action_taken_at' => 'datetime',
|
||||
];
|
||||
|
||||
public function application()
|
||||
{
|
||||
return $this->belongsTo(CuratedRegister::class, 'register_id');
|
||||
}
|
||||
|
||||
public function emailReplyUrl()
|
||||
{
|
||||
return url('/auth/sign_up/concierge?sid='.$this->register_id . '&id=' . $this->id . '&code=' . $this->secret_code);
|
||||
}
|
||||
|
||||
public function adminReviewUrl()
|
||||
{
|
||||
$url = '/i/admin/curated-onboarding/show/' . $this->register_id . '/?ah=' . $this->id;
|
||||
if($this->reply_to_id) {
|
||||
$url .= '&rtid=' . $this->reply_to_id;
|
||||
}
|
||||
return url($url);
|
||||
}
|
||||
}
|
|
@ -72,6 +72,8 @@ class ConfigCacheService
|
|||
'instance.banner.blurhash',
|
||||
|
||||
'autospam.nlp.enabled',
|
||||
|
||||
'instance.curated_registration.enabled',
|
||||
// 'system.user_mode'
|
||||
];
|
||||
|
||||
|
|
|
@ -48,13 +48,16 @@ class LandingService
|
|||
->toArray() : [];
|
||||
});
|
||||
|
||||
$openReg = (bool) config_cache('pixelfed.open_registration');
|
||||
|
||||
$res = [
|
||||
'name' => config_cache('app.name'),
|
||||
'url' => config_cache('app.url'),
|
||||
'domain' => config('pixelfed.domain.app'),
|
||||
'show_directory' => config_cache('instance.landing.show_directory'),
|
||||
'show_explore_feed' => config_cache('instance.landing.show_explore'),
|
||||
'open_registration' => config_cache('pixelfed.open_registration') == 1,
|
||||
'open_registration' => (bool) $openReg,
|
||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'version' => config('pixelfed.version'),
|
||||
'about' => [
|
||||
'banner_image' => config_cache('app.banner_image') ?? url('/storage/headers/default.jpg'),
|
||||
|
|
|
@ -145,4 +145,35 @@ return [
|
|||
'software-update' => [
|
||||
'disable_failed_warning' => env('INSTANCE_SOFTWARE_UPDATE_DISABLE_FAILED_WARNING', false)
|
||||
],
|
||||
|
||||
'notifications' => [
|
||||
'gc' => [
|
||||
'enabled' => env('INSTANCE_NOTIFY_AUTO_GC', false),
|
||||
'delete_after_days' => env('INSTANCE_NOTIFY_AUTO_GC_DEL_AFTER_DAYS', 365)
|
||||
]
|
||||
],
|
||||
|
||||
'curated_registration' => [
|
||||
'enabled' => env('INSTANCE_CUR_REG', false),
|
||||
|
||||
'resend_confirmation_limit' => env('INSTANCE_CUR_REG_RESEND_LIMIT', 5),
|
||||
|
||||
'captcha_enabled' => env('INSTANCE_CUR_REG_CAPTCHA', env('CAPTCHA_ENABLED', false)),
|
||||
|
||||
'state' => [
|
||||
'fallback_on_closed_reg' => true,
|
||||
'only_enabled_on_closed_reg' => env('INSTANCE_CUR_REG_STATE_ONLY_ON_CLOSED', true),
|
||||
],
|
||||
|
||||
'notify' => [
|
||||
'admin' => [
|
||||
'on_verify_email' => [
|
||||
'enabled' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY', false),
|
||||
'bundle' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY_BUNDLE', false),
|
||||
'max_per_day' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_VERIFY_MPD', 10),
|
||||
],
|
||||
'on_user_response' => env('INSTANCE_CUR_REG_NOTIFY_ADMIN_ON_USER_RESPONSE', false),
|
||||
]
|
||||
],
|
||||
],
|
||||
];
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('curated_registers', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('email')->unique()->nullable()->index();
|
||||
$table->string('username')->unique()->nullable()->index();
|
||||
$table->string('password')->nullable();
|
||||
$table->string('ip_address')->nullable();
|
||||
$table->string('verify_code')->nullable();
|
||||
$table->text('reason_to_join')->nullable();
|
||||
$table->unsignedBigInteger('invited_by')->nullable()->index();
|
||||
$table->boolean('is_approved')->default(0)->index();
|
||||
$table->boolean('is_rejected')->default(0)->index();
|
||||
$table->boolean('is_awaiting_more_info')->default(0)->index();
|
||||
$table->boolean('is_closed')->default(0)->index();
|
||||
$table->json('autofollow_account_ids')->nullable();
|
||||
$table->json('admin_notes')->nullable();
|
||||
$table->unsignedInteger('approved_by_admin_id')->nullable();
|
||||
$table->timestamp('email_verified_at')->nullable();
|
||||
$table->timestamp('admin_notified_at')->nullable();
|
||||
$table->timestamp('action_taken_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('curated_registers');
|
||||
}
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('curated_register_activities', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->unsignedInteger('register_id')->nullable()->index();
|
||||
$table->unsignedInteger('admin_id')->nullable();
|
||||
$table->unsignedInteger('reply_to_id')->nullable()->index();
|
||||
$table->string('secret_code')->nullable();
|
||||
$table->string('type')->nullable()->index();
|
||||
$table->string('title')->nullable();
|
||||
$table->string('link')->nullable();
|
||||
$table->text('message')->nullable();
|
||||
$table->json('metadata')->nullable();
|
||||
$table->boolean('from_admin')->default(false)->index();
|
||||
$table->boolean('from_user')->default(false)->index();
|
||||
$table->boolean('admin_only_view')->default(true);
|
||||
$table->boolean('action_required')->default(false);
|
||||
$table->timestamp('admin_notified_at')->nullable();
|
||||
$table->timestamp('action_taken_at')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('curated_register_activities');
|
||||
}
|
||||
};
|
BIN
public/js/landing.js
vendored
BIN
public/js/landing.js
vendored
Binary file not shown.
Binary file not shown.
|
@ -1,33 +1,46 @@
|
|||
<template>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
|
||||
<div class="container" style="max-width: 600px;">
|
||||
<router-link to="/" class="navbar-brand">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Logo">
|
||||
<span class="mr-3">{{ name }}</span>
|
||||
</router-link>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
</ul>
|
||||
<div class="my-2 my-lg-0">
|
||||
<a class="btn btn-outline-light btn-sm rounded-pill font-weight-bold px-4" href="/login">Login</a>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark fixed-top">
|
||||
<div class="container" style="max-width: 600px;">
|
||||
<router-link to="/" class="navbar-brand">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Logo">
|
||||
<span class="mr-3">{{ name }}</span>
|
||||
</router-link>
|
||||
<ul class="navbar-nav mr-auto">
|
||||
</ul>
|
||||
<div class="my-2 my-lg-0">
|
||||
<a class="btn btn-outline-light btn-sm rounded-pill font-weight-bold px-4" href="/login">Login</a>
|
||||
|
||||
<a v-if="config.open_registration" class="ml-2 btn btn-primary btn-primary-alt btn-sm rounded-pill font-weight-bold px-4" href="/register">Sign up</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<a v-if="config.open_registration || config.curated_onboarding" class="ml-2 btn btn-primary btn-primary-alt btn-sm rounded-pill font-weight-bold px-4" :href="regLink">Sign up</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script type="text/javascript">
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config: window.pfl,
|
||||
name: window.pfl.name,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(window).scroll(function(){
|
||||
$('nav').toggleClass('bg-black', $(this).scrollTop() > 20);
|
||||
});
|
||||
}
|
||||
}
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
config: window.pfl,
|
||||
name: window.pfl.name,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
regLink: {
|
||||
get() {
|
||||
if(this.config.open_registration) {
|
||||
return '/register';
|
||||
}
|
||||
|
||||
if(this.config.curated_onboarding) {
|
||||
return '/auth/sign_up';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
$(window).scroll(function(){
|
||||
$('nav').toggleClass('bg-black', $(this).scrollTop() > 20);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
86
resources/views/admin/curated-register/index.blade.php
Normal file
86
resources/views/admin/curated-register/index.blade.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
@extends('admin.partial.template-full')
|
||||
|
||||
@section('section')
|
||||
</div><div class="header bg-primary pb-3 mt-n4">
|
||||
<div class="container-fluid">
|
||||
<div class="header-body">
|
||||
<div class="row align-items-center py-4">
|
||||
<div class="col-lg-8 col-12">
|
||||
<p class="display-1 text-white d-inline-block mb-0">Curated Onboarding</p>
|
||||
<p class="text-white mb-0">The ideal solution for communities seeking a balance between open registration and invite-only membership</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if((bool) config_cache('instance.curated_registration.enabled'))
|
||||
<div class="m-n2 m-lg-4">
|
||||
<div class="container-fluid mt-4">
|
||||
@include('admin.curated-register.partials.nav')
|
||||
|
||||
@if($records && $records->count())
|
||||
<div class="table-responsive rounded">
|
||||
<table class="table table-dark">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">Username</th>
|
||||
@if(in_array($filter, ['all', 'open']))
|
||||
<th scope="col">Status</th>
|
||||
@endif
|
||||
<th scope="col">Reason for Joining</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Created</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($records as $record)
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
<a href="/i/admin/curated-onboarding/show/{{$record->id}}" class="font-weight-bold">
|
||||
#{{ $record->id }}
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<p class="font-weight-bold mb-0">
|
||||
@{{ $record->username }}
|
||||
</p>
|
||||
</td>
|
||||
@if(in_array($filter, ['all', 'open']))
|
||||
<td class="align-middle">
|
||||
{!! $record->adminStatusLabel() !!}
|
||||
</td>
|
||||
@endif
|
||||
<td class="align-middle">
|
||||
{{ str_limit($record->reason_to_join, 100) }}
|
||||
</td>
|
||||
<td class="align-middle">
|
||||
<p class="mb-0">
|
||||
{{ str_limit(\Illuminate\Support\Str::mask($record->email, '*', 5, 10), 10) }}
|
||||
</p>
|
||||
</td>
|
||||
<td class="align-middle">{{ $record->created_at->diffForHumans() }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="d-flex mt-3">
|
||||
{{ $records->links() }}
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<p class="text-center"><i class="far fa-check-circle fa-6x text-success"></i></p>
|
||||
<p class="lead text-center">No {{ request()->filled('filter') ? request()->filter : 'open' }} applications found!</p>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
@include('admin.curated-register.partials.not-enabled')
|
||||
@endif
|
||||
@endsection
|
|
@ -0,0 +1,463 @@
|
|||
<template v-if="loaded">
|
||||
@if($email_verified_at === null)
|
||||
<div class="alert alert-danger mb-3">
|
||||
<p class="mb-0 font-weight-bold">Applicant has not verified their email address yet, action can not be taken at this time.</p>
|
||||
</div>
|
||||
@elseif($is_closed != true)
|
||||
<div class="d-flex justify-content-between flex-column flex-md-row mb-4" style="gap:1rem">
|
||||
<button
|
||||
class="btn btn-success bg-gradient-success rounded-pill"
|
||||
v-on:click.prevent="handleAction('approve', $event)">
|
||||
Approve
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger bg-gradient-danger rounded-pill flex-grow-1"
|
||||
v-on:click.prevent="handleAction('reject', $event)">
|
||||
Reject
|
||||
</button>
|
||||
<button
|
||||
class="btn rounded-pill px-md-5"
|
||||
:class="[ composeFormOpen ? 'btn-dark bg-gradient-dark' : 'btn-outline-dark' ]"
|
||||
v-on:click.prevent="handleAction('request', $event)">
|
||||
Request details
|
||||
</button>
|
||||
<button
|
||||
class="btn rounded-pill px-md-5"
|
||||
:class="[ messageFormOpen ? 'btn-dark bg-gradient-dark' : 'btn-outline-dark' ]"
|
||||
v-on:click.prevent="handleAction('message', $event)">
|
||||
Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@else
|
||||
@if($is_rejected == true)
|
||||
<p>Application was <span class="font-weight-bold text-danger">rejected</span> on {{ $action_taken_at }}</p>
|
||||
@elseif($is_approved == true)
|
||||
<p>Application was <span class="font-weight-bold text-success">approved</span> on {{ $action_taken_at }}</p>
|
||||
@else
|
||||
<p>Application was closed on {{ $action_taken_at }}</p>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<transition name="fade">
|
||||
<div v-show="composeFormOpen">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<p class="lead font-weight-bold text-center">Request Additional Details</p>
|
||||
<p class="text-muted">Use this form to request additional details. Once you press Send, we'll send the potential user an email with a special link they can visit with a form that they can provide additional details with. You can also Preview the email before it's sent.</p>
|
||||
|
||||
<div class="request-form">
|
||||
<div class="form-group">
|
||||
<label for="requestDetailsMessageInput" class="small text-muted">Your Message:</label>
|
||||
<textarea
|
||||
class="form-control text-dark"
|
||||
id="requestDetailsMessageInput"
|
||||
rows="5"
|
||||
v-model="composeMessage"
|
||||
style="white-space: pre-wrap;"
|
||||
placeholder="Enter your additional detail message here...">
|
||||
</textarea>
|
||||
<p class="help-text small text-right">
|
||||
<span>@{{ composeMessage && composeMessage.length ? composeMessage.length : 0 }}</span>
|
||||
<span>/</span>
|
||||
<span>500</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary rounded-pill btn-sm px-4"
|
||||
v-on:click.prevent="handleSend()">
|
||||
Send
|
||||
</button>
|
||||
<a
|
||||
class="btn btn-dark rounded-pill btn-sm px-4"
|
||||
:href="previewDetailsMessageUrl"
|
||||
target="_blank">
|
||||
Preview
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<transition name="fade">
|
||||
<div v-show="messageFormOpen">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<p class="lead font-weight-bold text-center">Send Message</p>
|
||||
<p class="text-muted">Use this form to send a message to the applicant. Once you press Send, we'll send the potential user an email with your message. You can also Preview the email before it's sent.</p>
|
||||
|
||||
<div class="request-form">
|
||||
<div class="form-group">
|
||||
<label for="sendMessageInput" class="small text-muted">Your Message:</label>
|
||||
<textarea
|
||||
class="form-control text-dark"
|
||||
id="sendMessageInput"
|
||||
rows="5"
|
||||
v-model="messageBody"
|
||||
style="white-space: pre-wrap;"
|
||||
placeholder="Enter your message here...">
|
||||
</textarea>
|
||||
<p class="help-text small text-right">
|
||||
<span>@{{ messageBody && messageBody.length ? messageBody.length : 0 }}</span>
|
||||
<span>/</span>
|
||||
<span>500</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-primary rounded-pill btn-sm px-4"
|
||||
v-on:click.prevent="handleMessageSend()">
|
||||
Send
|
||||
</button>
|
||||
<a
|
||||
class="btn btn-dark rounded-pill btn-sm px-4"
|
||||
:href="previewMessageUrl"
|
||||
target="_blank">
|
||||
Preview
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<div class="card border">
|
||||
<div class="card-body">
|
||||
<p class="text-center font-weight-bold">Activity Log</p>
|
||||
<div class="activity-log">
|
||||
<div v-if="!loaded" class="d-flex justify-content-center align-items-center py-5 my-5">
|
||||
<b-spinner />
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="activity in activities"
|
||||
class="activity-log-item"
|
||||
:key="activity.timestamp">
|
||||
<div v-if="activity.action === 'approved'" class="activity-log-item-icon bg-success">
|
||||
<i class="far fa-check fa-lg text-white"></i>
|
||||
</div>
|
||||
<div v-else-if="activity.action === 'rejected'" class="activity-log-item-icon bg-danger">
|
||||
<i class="far fa-times fa-lg text-white"></i>
|
||||
</div>
|
||||
<div v-else-if="activity.action === 'request_details'" class="activity-log-item-icon bg-lighter">
|
||||
<i class="fas fa-exclamation fa-lg text-dark"></i>
|
||||
</div>
|
||||
<div v-else class="activity-log-item-icon">
|
||||
<i class="fas fa-circle"></i>
|
||||
</div>
|
||||
<div class="activity-log-item-date">@{{ parseDate(activity.timestamp) }}<span>@{{ parseTime(activity.timestamp) }}</span></div>
|
||||
<div class="activity-log-item-content"><span class="activity-log-item-content-title">@{{ activity.title }}</span><span class="activity-log-item-content-message" v-if="activity.message">@{{ strLimit(activity.message) }}</span></div>
|
||||
<div class="d-flex" style="gap: 1rem;">
|
||||
<div v-if="activity.link">
|
||||
<a href="#" class="activity-log-item-content-link text-muted" @click.prevent="openModal(activity)">Details</a>
|
||||
</div>
|
||||
<div v-if="activity.user_response">
|
||||
<a href="#" class="activity-log-item-content-link" @click.prevent="openUserResponse(activity)">View User Response</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="card card-body d-flex justify-content-center align-items-center py-5">
|
||||
<b-spinner />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
let app = new Vue({
|
||||
el: '#panel',
|
||||
|
||||
data() {
|
||||
return {
|
||||
loaded: false,
|
||||
activities: [],
|
||||
composeFormOpen: false,
|
||||
messageFormOpen: false,
|
||||
composeMessage: null,
|
||||
messageBody: null,
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
setTimeout(() => {
|
||||
this.fetchActivities();
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
computed: {
|
||||
previewDetailsMessageUrl() {
|
||||
return `/i/admin/curated-onboarding/show/{{$id}}/preview-details-message?message=${encodeURIComponent(this.composeMessage)}`;
|
||||
},
|
||||
previewMessageUrl() {
|
||||
return `/i/admin/curated-onboarding/show/{{$id}}/preview-message?message=${encodeURIComponent(this.messageBody)}`;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
parseDate(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
},
|
||||
|
||||
parseTime(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
|
||||
return date.toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
hour12: true
|
||||
});
|
||||
},
|
||||
|
||||
strLimit(str, len = 15) {
|
||||
if(str && str.length) {
|
||||
return str.slice(0, len) + (str.length > 15 ? '...' : '');
|
||||
}
|
||||
return str;
|
||||
},
|
||||
|
||||
fetchActivities() {
|
||||
axios.get('/i/admin/api/curated-onboarding/show/{{$id}}/activity-log')
|
||||
.then(res => {
|
||||
console.log(res.data);
|
||||
this.activities = res.data;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loaded = true;
|
||||
})
|
||||
},
|
||||
|
||||
handleAction(action, $event) {
|
||||
$event.currentTarget?.blur();
|
||||
|
||||
switch(action) {
|
||||
case 'approve':
|
||||
this.handleApprove();
|
||||
break;
|
||||
|
||||
case 'reject':
|
||||
this.handleReject();
|
||||
break;
|
||||
|
||||
case 'request':
|
||||
this.messageFormOpen = false;
|
||||
this.composeFormOpen = !this.composeFormOpen;
|
||||
break;
|
||||
|
||||
case 'message':
|
||||
this.composeFormOpen = false;
|
||||
this.messageFormOpen = !this.messageFormOpen;
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleApprove() {
|
||||
swal({
|
||||
title: "Approve Request?",
|
||||
text: "The user application request will be approved.",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then((willApprove) => {
|
||||
if(willApprove) {
|
||||
this.handleApproveAction();
|
||||
} else {
|
||||
swal("Approval Cancelled!", "The application approval has been cancelled. If you change your mind, you can easily approve or reject this application in the future.", "success");
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
handleReject() {
|
||||
swal({
|
||||
title: "Are you sure?",
|
||||
text: "The user application request will be rejected.",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
})
|
||||
.then((willReject) => {
|
||||
if (willReject) {
|
||||
swal({
|
||||
title: "Choose Action",
|
||||
text: "You can provide a rejection email, or simply silently reject",
|
||||
icon: "warning",
|
||||
buttons: {
|
||||
cancel: "Cancel",
|
||||
reject: {
|
||||
text: "Reject with email",
|
||||
value: "reject-email"
|
||||
},
|
||||
silent: {
|
||||
text: "Silently Reject",
|
||||
value: "reject-silent"
|
||||
}
|
||||
},
|
||||
dangerMode: true,
|
||||
})
|
||||
.then(res => {
|
||||
if(!res) {
|
||||
swal("Rejection Cancelled!", "The application rejection has been cancelled. If you change your mind, you can easily reject this application in the future.", "success");
|
||||
} else {
|
||||
this.handleRejectAction(res);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
swal("Rejection Cancelled!", "The application rejection has been cancelled. If you change your mind, you can easily reject this application in the future.", "success");
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
handleRejectAction(action) {
|
||||
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/reject', {
|
||||
action: action
|
||||
}).then(res => {
|
||||
window.location.href = '/i/admin/curated-onboarding/home?a=rj';
|
||||
console.log(res);
|
||||
})
|
||||
},
|
||||
|
||||
handleApproveAction() {
|
||||
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/approve')
|
||||
.then(res => {
|
||||
window.location.href = '/i/admin/curated-onboarding/home?a=aj';
|
||||
})
|
||||
},
|
||||
|
||||
handlePreview() {
|
||||
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/message/preview', {
|
||||
message: this.composeMessage
|
||||
})
|
||||
.then(res => {
|
||||
console.log(res.data);
|
||||
})
|
||||
},
|
||||
|
||||
handleSend() {
|
||||
swal({
|
||||
title: "Confirm",
|
||||
text: "Are you sure you want to send this request to this user?",
|
||||
icon: "warning",
|
||||
buttons: true,
|
||||
dangerMode: true,
|
||||
}).then((hasConfirmed) => {
|
||||
if(hasConfirmed) {
|
||||
this.composeFormOpen = false;
|
||||
this.loaded = false;
|
||||
axios.post('/i/admin/api/curated-onboarding/show/{{$id}}/message/send', {
|
||||
message: this.composeMessage
|
||||
})
|
||||
.then(res => {
|
||||
this.composeMessage = null;
|
||||
swal('Successfully sent!','', 'success');
|
||||
this.fetchActivities();
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openModal(activity) {
|
||||
swal(activity.title, activity.message)
|
||||
},
|
||||
|
||||
openUserResponse(activity) {
|
||||
swal('User Response', activity.user_response.message)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('styles')
|
||||
<style type="text/css">
|
||||
.activity-log-item {
|
||||
border-left: 1px solid #e5e5e5;
|
||||
position: relative;
|
||||
padding: 2rem 1.5rem .5rem 2.5rem;
|
||||
font-size: .9rem;
|
||||
margin-left: 3rem;
|
||||
min-height: 5rem
|
||||
}
|
||||
.activity-log-item:last-child {
|
||||
padding-bottom: 4rem
|
||||
}
|
||||
.activity-log-item .activity-log-item-date {
|
||||
margin-bottom: .5rem;
|
||||
font-weight: bold;
|
||||
color: var(--primary);
|
||||
}
|
||||
.activity-log-item .activity-log-item-date span {
|
||||
color: #888;
|
||||
font-size: 85%;
|
||||
padding-left: .4rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
.activity-log-item .activity-log-item-content {
|
||||
padding: .5rem .8rem;
|
||||
background-color: #f4f4f4;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
.activity-log-item .activity-log-item-content span {
|
||||
display:block;
|
||||
color:#666;
|
||||
}
|
||||
.activity-log-item .activity-log-item-icon {
|
||||
line-height:2.6rem;
|
||||
position:absolute;
|
||||
left:-1.3rem;
|
||||
width:2.6rem;
|
||||
height:2.6rem;
|
||||
text-align:center;
|
||||
border-radius:50%;
|
||||
font-size:1.1rem;
|
||||
background-color:#fff;
|
||||
color:#fff
|
||||
}
|
||||
.activity-log-item .activity-log-item-icon {
|
||||
color:#e5e5e5;
|
||||
border:1px solid #e5e5e5;
|
||||
font-size:.6rem
|
||||
}
|
||||
.activity-log-item-content-title {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
color: #000000 !important;
|
||||
}
|
||||
.activity-log-item-content-message {
|
||||
font-weight: 400;
|
||||
font-size: 13px;
|
||||
}
|
||||
@media(min-width:992px) {
|
||||
.activity-log-item {
|
||||
margin-left:10rem
|
||||
}
|
||||
.activity-log-item .activity-log-item-date {
|
||||
position:absolute;
|
||||
left:-10rem;
|
||||
width:7.5rem;
|
||||
text-align:right
|
||||
}
|
||||
.activity-log-item .activity-log-item-date span {
|
||||
display:block
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,39 @@
|
|||
@if(request()->filled('a'))
|
||||
@if(request()->input('a') === 'rj')
|
||||
<div class="alert alert-danger">
|
||||
<p class="font-weight-bold mb-0"><i class="far fa-info-circle mr-2"></i>Successfully rejected application!</p>
|
||||
</div>
|
||||
@endif
|
||||
@if(request()->input('a') === 'aj')
|
||||
<div class="alert alert-success">
|
||||
<p class="font-weight-bold mb-0"><i class="far fa-info-circle mr-2"></i>Successfully accepted application!</p>
|
||||
</div>
|
||||
@endif
|
||||
@endif
|
||||
|
||||
<div class="row mb-3 justify-content-between">
|
||||
<div class="col-12">
|
||||
<ul class="nav nav-pills">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->has('filter') ? '':'active'}}" href="/i/admin/curated-onboarding/home">Open Applications</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->has('filter') && request()->filter == 'all' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=all">All Applications</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->has('filter') && request()->filter == 'awaiting' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=awaiting">Awaiting Info</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->has('filter') && request()->filter == 'approved' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=approved">Approved Applications</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->has('filter') && request()->filter == 'rejected' ? 'active':''}}" href="/i/admin/curated-onboarding/home?filter=rejected">Rejected Applications</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@push('scripts')
|
||||
|
||||
@endpush
|
|
@ -0,0 +1,24 @@
|
|||
<div class="m-n2 m-lg-4">
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="text-center">Feature not enabled</h1>
|
||||
|
||||
<p class="lead">To enable this feature:
|
||||
|
||||
<ol class="lead">
|
||||
<li>Go to the <a href="/i/admin/settings" class="font-weight-bold">Settings page</a></li>
|
||||
<li>
|
||||
Under <strong>Registration Status</strong> select:
|
||||
<pre>Filtered - Anyone can apply (Curated Onboarding)</pre>
|
||||
</li>
|
||||
<li>Save the changes</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
102
resources/views/admin/curated-register/show.blade.php
Normal file
102
resources/views/admin/curated-register/show.blade.php
Normal file
|
@ -0,0 +1,102 @@
|
|||
@extends('admin.partial.template-full')
|
||||
|
||||
@section('section')
|
||||
</div><div class="header bg-primary pb-3 mt-n4">
|
||||
<div class="container-fluid">
|
||||
<div class="header-body">
|
||||
<div class="row align-items-center py-4">
|
||||
<div class="col-lg-8 col-12">
|
||||
<p class="mb-0">
|
||||
<a href="{{ route('admin.curated-onboarding')}}" class="text-white">
|
||||
<i class="far fa-chevron-left mr-1"></i> Back to Curated Onboarding
|
||||
</a>
|
||||
</p>
|
||||
<p class="display-3 text-white d-inline-block">Application #{{ $record->id }}</p>
|
||||
<div class="text-white mb-0 d-flex align-items-center" style="gap:1rem">
|
||||
@if($record->is_closed)
|
||||
@else
|
||||
<span class="font-weight-bold">
|
||||
<i class="far fa-circle mr-2"></i>
|
||||
Open / Awaiting Admin Action
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-n2 m-lg-4">
|
||||
<div class="container-fluid mt-4">
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-4">
|
||||
<div class="card border">
|
||||
<div class="card-header font-weight-bold bg-gradient-primary text-center text-white py-2">Details</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<div class="list-group-item">
|
||||
<div>Username</div>
|
||||
<div>{{ $record->username }}</div>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<div>Email</div>
|
||||
<div>{{ $record->email }}</div>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<div>Created</div>
|
||||
<div data-toggle="tooltip" title="{{ $record->created_at }}">{{ $record->created_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
@if($record->email_verified_at)
|
||||
<div class="list-group-item">
|
||||
<div>Email Verified</div>
|
||||
<div data-toggle="tooltip" title="{{ $record->email_verified_at }}">{{ $record->email_verified_at->diffForHumans() }}</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="list-group-item">
|
||||
<div>Email Verified</div>
|
||||
<div class="text-danger">Not yet</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border mt-3">
|
||||
<div class="card-header font-weight-bold bg-gradient-primary text-center text-white py-2">Reason for Joining</div>
|
||||
<div class="card-body">
|
||||
<blockquote>{{ $record->reason_to_join }}</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-8">
|
||||
@include('admin.curated-register.partials.activity-log', [
|
||||
'id' => $record->id,
|
||||
'is_closed' => $record->is_closed,
|
||||
'is_approved' => $record->is_approved,
|
||||
'is_rejected' => $record->is_rejected,
|
||||
'action_taken_at' => $record->action_taken_at,
|
||||
'email_verified_at' => $record->email_verified_at
|
||||
])
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<style type="text/css">
|
||||
.list-group-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
div:first-child {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -18,7 +18,7 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*autospam*')?'active':''}}" href="{{route('admin.autospam')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Autospam <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
<span class="nav-link-text">Autospam</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
@ -56,6 +56,15 @@
|
|||
<span class="nav-link-text">Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@if((bool) config_cache('instance.curated_registration.enabled'))
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*admin/curated-onboarding*')?'active':''}}" href="{{route('admin.curated-onboarding')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Curated Onboarding</span>
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
|
||||
<hr class="my-3">
|
||||
|
@ -64,7 +73,7 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*custom-emoji*')?'active':''}}" href="{{route('admin.custom-emoji')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Custom Emoji <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
<span class="nav-link-text">Custom Emoji</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
@ -96,10 +105,17 @@
|
|||
</a>
|
||||
</li>
|
||||
|
||||
{{-- <li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*roles*')?'active':''}}" href="{{route('admin.roles')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Roles <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
</a>
|
||||
</li> --}}
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*stories*')?'active':''}}" href="{{route('admin.stories')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Stories <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
<span class="nav-link-text">Stories</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -118,14 +134,14 @@
|
|||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*diagnostics*')?'active':''}}" href="{{route('admin.diagnostics')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Diagnostics <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
<span class="nav-link-text">Diagnostics</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {{request()->is('*directory*')?'active':''}}" href="{{route('admin.directory')}}">
|
||||
<i class="ni ni-bold-right text-primary"></i>
|
||||
<span class="nav-link-text">Directory <span class="badge badge-primary ml-1">NEW</span></span>
|
||||
<span class="nav-link-text">Directory</span>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
|
|
|
@ -77,6 +77,18 @@
|
|||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Features</label>
|
||||
|
||||
<div class="form-group row mb-5">
|
||||
<label for="staticEmail" class="col-sm-12 col-form-label font-weight-bold">Registration Status</label>
|
||||
<div class="col-sm-4">
|
||||
<select class="custom-select" name="regs">
|
||||
<option value="open" {{ $regState === 'open' ? 'selected' : '' }}>Open - Anyone can register</option>
|
||||
<option value="filtered" {{ $regState === 'filtered' ? 'selected' : '' }}>Filtered - Anyone can apply (Curated Onboarding)</option>
|
||||
<option value="closed" {{ $regState === 'closed' ? 'selected' : '' }}>Closed - Nobody can register</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($cloud_ready)
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="cloud_storage" class="custom-control-input" id="cls1" {{config_cache('pixelfed.cloud_storage') ? 'checked' : ''}}>
|
||||
|
@ -91,11 +103,18 @@
|
|||
</div>
|
||||
<p class="mb-4 small">ActivityPub federation, compatible with Pixelfed, Mastodon and other projects.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
{{-- <div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="open_registration" class="custom-control-input" id="openReg" {{config_cache('pixelfed.open_registration') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="openReg">Open Registrations</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Allow new user registrations.</p>
|
||||
<p class="mb-4 small">Allow new user registrations.</p> --}}
|
||||
|
||||
|
||||
{{-- <div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="registration_approvals" class="custom-control-input" id="openRegApproval" {{config_cache('pixelfed.registration_approvals') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="openRegApproval">Registration Approval Mode</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Manually review new account registration applications.</p> --}}
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="mobile_apis" class="custom-control-input" id="cf2" {{config_cache('pixelfed.oauth_enabled') ? 'checked' : ''}}>
|
||||
|
|
124
resources/views/auth/curated-register/concierge.blade.php
Normal file
124
resources/views/auth/curated-register/concierge.blade.php
Normal file
|
@ -0,0 +1,124 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 4])
|
||||
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="alert alert-danger bg-danger border-danger text-white">
|
||||
<p class="lead font-weight-bold mb-0"><i class="far fa-exclamation-triangle mr-2"></i> {{ $error }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="mb-5"></div>
|
||||
@endif
|
||||
|
||||
@if($emailConfirmed)
|
||||
<h1 class="text-center font-weight-bold mt-4">Information Requested</h1>
|
||||
<p class="lead text-center"><span class="font-weight-light">Our admin team requests the following information from you:</span></p>
|
||||
<div class="border border-primary p-4 rounded my-3" style="border-style: dashed !important;">
|
||||
<p class="mb-0 lead">testing</p>
|
||||
</div>
|
||||
<hr class="border-dark">
|
||||
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="message">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="message">Your Response</label>
|
||||
<textarea
|
||||
class="form-control bg-dark border-dark text-white"
|
||||
rows="4"
|
||||
id="reason"
|
||||
name="response"
|
||||
placeholder="Enter your response here, up to 1000 chars..."
|
||||
maxlength="1000"></textarea>
|
||||
<div class="help-text small text-muted d-flex justify-content-end mt-1 font-weight-bold">
|
||||
<span id="charCount" class="text-white">0</span>/<span>1000</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary font-weight-bold rounded-pill px-5">Submit</button>
|
||||
</form>
|
||||
<hr class="border-dark">
|
||||
<p class="text-muted small text-center">For additional information, please see our <a href="{{ route('help.curated-onboarding') }}" style="font-weight: 600;">Curated Onboarding</a> Help Center page.</p>
|
||||
@else
|
||||
@include('auth.curated-register.partials.message-email-confirm', ['step' => 4])
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var textInput = document.getElementById('reason');
|
||||
var charCount = document.getElementById('charCount');
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
|
||||
textInput.addEventListener('input', function () {
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
129
resources/views/auth/curated-register/concierge_form.blade.php
Normal file
129
resources/views/auth/curated-register/concierge_form.blade.php
Normal file
|
@ -0,0 +1,129 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 4])
|
||||
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="alert alert-danger bg-danger border-danger text-white">
|
||||
<p class="lead font-weight-bold mb-0"><i class="far fa-exclamation-triangle mr-2"></i> {{ $error }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="mb-5"></div>
|
||||
@endif
|
||||
|
||||
<h1 class="text-center font-weight-bold mt-4 mb-3"><i class="far fa-info-circle mr-2"></i> Information Requested</h1>
|
||||
<p class="h5" style="line-height: 1.5;">Before we can process your application to join, our admin team have requested additional information from you. Please respond at your earliest convenience!</p>
|
||||
<hr class="border-dark">
|
||||
<p>From our Admins:</p>
|
||||
<div class="card card-body mb-1 bg-dark border border-secondary" style="border-style: dashed !important;">
|
||||
<p class="lead mb-0" style="white-space: pre; opacity: 0.8">{{ $activity->message }}</p>
|
||||
</div>
|
||||
<p class="mb-3 small text-muted">If you don't understand this request, or need additional context you should request clarification from the admin team.</p>
|
||||
{{-- <hr class="border-dark"> --}}
|
||||
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="crid" value="{{ $activity->register_id }}">
|
||||
<input type="hidden" name="acid" value="{{ $activity->id }}">
|
||||
<div class="form-group">
|
||||
<label for="message">Your Response</label>
|
||||
<textarea
|
||||
class="form-control bg-dark border-dark text-white"
|
||||
rows="4"
|
||||
id="reason"
|
||||
name="response"
|
||||
placeholder="Enter your response here, up to 1000 chars..."
|
||||
maxlength="1000">{{ old('response') }}</textarea>
|
||||
<div class="help-text small text-muted d-flex justify-content-end mt-1 font-weight-bold">
|
||||
<span id="charCount" class="text-white">0</span>/<span>1000</span>
|
||||
</div>
|
||||
</div>
|
||||
@if($showCaptcha)
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
@endif
|
||||
<div class="text-center">
|
||||
<button class="btn btn-primary font-weight-bold rounded-pill px-5">Submit my response</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr class="border-dark">
|
||||
<p class="text-muted small text-center">For additional information, please see our <a href="{{ route('help.curated-onboarding') }}" style="font-weight: 600;">Curated Onboarding</a> Help Center page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var textInput = document.getElementById('reason');
|
||||
var charCount = document.getElementById('charCount');
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
|
||||
textInput.addEventListener('input', function () {
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,91 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 3])
|
||||
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="alert alert-danger bg-danger border-danger text-white">
|
||||
<p class="lead font-weight-bold mb-0"><i class="far fa-exclamation-triangle mr-2"></i> {{ $error }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="mb-5"></div>
|
||||
@endif
|
||||
|
||||
<h1 class="text-center">Confirm Email</h1>
|
||||
<p class="lead text-center">Please confirm your email address so we can continue processing your registration application.</p>
|
||||
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="sid" value={{request()->input('sid')}}>
|
||||
<input type="hidden" name="code" value={{request()->input('code')}}>
|
||||
@if(config('instance.curated_registration.captcha_enabled'))
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
@endif
|
||||
<div class="mt-3 pt-4">
|
||||
<button class="btn btn-primary rounded-pill font-weight-bold btn-block">Confirm Email Address</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,74 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 4])
|
||||
|
||||
<p class="text-center"><i class="fal fa-check-circle text-success fa-8x"></i></p>
|
||||
<h1 class="text-center font-weight-bold my-4">Email Confirmed!</h1>
|
||||
<p class="h4 text-center"><span class="font-weight-bold">Our admin team will review your application.</span></p>
|
||||
<hr class="border-dark">
|
||||
<p class="lead text-center">Most applications are processed within 24-48 hours. We will send you an email once your account is ready!</p>
|
||||
<p class="text-muted text-center">If we need any additional information, we will send you an automated request with a link that you can visit and provide further details to help process your application.</p>
|
||||
<hr class="border-dark">
|
||||
<p class="text-muted small text-center">For additional information, please see our <a href="{{ route('help.curated-onboarding') }}" style="font-weight: 600;">Curated Onboarding</a> Help Center page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
88
resources/views/auth/curated-register/index.blade.php
Normal file
88
resources/views/auth/curated-register/index.blade.php
Normal file
|
@ -0,0 +1,88 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => $step ?? 1])
|
||||
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="alert alert-danger bg-danger border-danger text-white">
|
||||
<p class="lead font-weight-bold mb-0"><i class="far fa-exclamation-triangle mr-2"></i> {{ $error }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="mb-5"></div>
|
||||
@endif
|
||||
@if($step === 1)
|
||||
@include('auth.curated-register.partials.step-1')
|
||||
@elseif ($step === 2)
|
||||
@include('auth.curated-register.partials.step-2')
|
||||
@elseif ($step === 3)
|
||||
@include('auth.curated-register.partials.step-3')
|
||||
@elseif ($step === 4)
|
||||
@include('auth.curated-register.partials.step-4')
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,23 @@
|
|||
<p class="mt-5 lead text-center font-weight-bold">Please verify your email address</p>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="action" value="email">
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
name="email"
|
||||
placeholder="Your email address"
|
||||
required />
|
||||
</div>
|
||||
@if(config('instance.curated_registration.captcha_enabled'))
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
@endif
|
||||
<div class="d-flex justify-content-center">
|
||||
<button class="btn btn-primary font-weight-bold rounded-pill px-5">Verify</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr class="border-dark">
|
||||
<p class="text-muted small text-center">For additional information, please see our <a href="{{ route('help.curated-onboarding') }}" style="font-weight: 600;">Curated Onboarding</a> Help Center page.</p>
|
|
@ -0,0 +1,134 @@
|
|||
<section class="step-wizard">
|
||||
<ul class="step-wizard-list">
|
||||
<li class="step-wizard-item {{ $step === 1 ? 'current-item':'' }}">
|
||||
<span class="progress-count">1</span>
|
||||
<span class="progress-label">Review Rules</span>
|
||||
</li>
|
||||
<li class="step-wizard-item {{ $step === 2 ? 'current-item':'' }}">
|
||||
<span class="progress-count">2</span>
|
||||
<span class="progress-label">Your Details</span>
|
||||
</li>
|
||||
<li class="step-wizard-item {{ $step === 3 ? 'current-item':'' }}">
|
||||
<span class="progress-count">3</span>
|
||||
<span class="progress-label">Confirm Email</span>
|
||||
</li>
|
||||
<li class="step-wizard-item {{ $step === 4 ? 'current-item':'' }}">
|
||||
<span class="progress-count">4</span>
|
||||
<span class="progress-label">Await Review</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@push('styles')
|
||||
<style type="text/css">
|
||||
.step-wizard {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
|
||||
.step-wizard-list{
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
list-style-type: none;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
padding: 20px 10px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.step-wizard-item {
|
||||
padding: 0 10px;
|
||||
flex-basis: 0;
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex-positive:1;
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
min-width: 20px;
|
||||
position: relative;
|
||||
}
|
||||
@media (min-width: 600px) {
|
||||
.step-wizard-item {
|
||||
padding: 0 20px;
|
||||
min-width: 140px;
|
||||
}
|
||||
}
|
||||
.step-wizard-item + .step-wizard-item:after{
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 19px;
|
||||
background: var(--primary);
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
transform: translateX(-50%);
|
||||
z-index: -10;
|
||||
}
|
||||
.progress-count{
|
||||
height: 40px;
|
||||
width:40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-weight: 600;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index:10;
|
||||
color: transparent;
|
||||
}
|
||||
.progress-count:after{
|
||||
content: "";
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
background: var(--primary);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
z-index: -10;
|
||||
}
|
||||
.progress-count:before{
|
||||
content: "";
|
||||
height: 10px;
|
||||
width: 20px;
|
||||
border-left: 3px solid #fff;
|
||||
border-bottom: 3px solid #fff;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -60%) rotate(-45deg);
|
||||
transform-origin: center center;
|
||||
}
|
||||
.progress-label{
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.current-item .progress-count:before,
|
||||
.current-item ~ .step-wizard-item .progress-count:before{
|
||||
display: none;
|
||||
}
|
||||
.current-item ~ .step-wizard-item .progress-count:after{
|
||||
height:10px;
|
||||
width:10px;
|
||||
}
|
||||
.current-item ~ .step-wizard-item .progress-label{
|
||||
opacity: 0.5;
|
||||
}
|
||||
.current-item .progress-count:after{
|
||||
background: #080e2b;
|
||||
border: 2px solid var(--primary);
|
||||
}
|
||||
.current-item .progress-count{
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,18 @@
|
|||
@php
|
||||
$rules = json_decode(config_cache('app.rules'), true)
|
||||
@endphp
|
||||
|
||||
<div class="list-group pt-4">
|
||||
@foreach($rules as $id => $rule)
|
||||
<div class="list-group-item bg-transparent border-dark d-flex align-items-center gap-1">
|
||||
<div style="display: block;width: 40px; height:40px;">
|
||||
<div class="border border-primary text-white font-weight-bold rounded-circle d-flex justify-content-center align-items-center" style="display: block;width: 40px; height:40px;">
|
||||
{{ $id + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-shrink-1">
|
||||
<p class="mb-0 flex-shrink-1 flex-wrap">{{ $rule }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
175
resources/views/auth/curated-register/partials/step-1.blade.php
Normal file
175
resources/views/auth/curated-register/partials/step-1.blade.php
Normal file
|
@ -0,0 +1,175 @@
|
|||
@php
|
||||
$id = str_random(14);
|
||||
@endphp
|
||||
<h1 class="text-center">Before you continue.</h1>
|
||||
@if(config_cache('app.rules') && strlen(config_cache('app.rules')) > 5)
|
||||
<p class="lead text-center">Let's go over a few basic guidelines established by the server's administrators.</p>
|
||||
|
||||
@include('auth.curated-register.partials.server-rules')
|
||||
@else
|
||||
<p class="lead text-center mt-4"><span class="opacity-5">The admins have not specified any community rules, however we suggest youreview the</span> <a href="/site/terms" target="_blank" class="text-white font-weight-bold">Terms of Use</a> <span class="opacity-5">and</span> <a href="/site/privacy" target="_blank" class="text-white font-weight-bold">Privacy Policy</a>.</p>
|
||||
@endif
|
||||
|
||||
<div class="action-btns">
|
||||
<form method="post" id="{{$id}}" class="flex-grow-1">
|
||||
@csrf
|
||||
<input type="hidden" name="step" value="1">
|
||||
<button type="button" class="btn btn-primary rounded-pill font-weight-bold btn-block flex-grow-1" onclick="onSubmit()">Accept</button>
|
||||
</form>
|
||||
|
||||
<a class="btn btn-outline-muted rounded-pill" href="/">Go back</a>
|
||||
</div>
|
||||
|
||||
<div class="small-links">
|
||||
<a href="/login">Login</a>
|
||||
<span>·</span>
|
||||
<a href="/auth/sign_up/resend-confirmation">Re-send confirmation</a>
|
||||
<span>·</span>
|
||||
<a href="{{route('help.curated-onboarding')}}" target="_blank">Help</a>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function onSubmit() {
|
||||
@if ($errors->any())
|
||||
document.getElementById('{{$id}}').submit();
|
||||
return;
|
||||
@endif
|
||||
swal({
|
||||
text: "Please select the region you are located in",
|
||||
icon: "info",
|
||||
buttons: {
|
||||
cancel: false,
|
||||
usa: {
|
||||
text: "United States",
|
||||
className: "swal-button--cancel",
|
||||
value: "usa"
|
||||
},
|
||||
uk: {
|
||||
text: "UK",
|
||||
className: "swal-button--cancel",
|
||||
value: "uk"
|
||||
},
|
||||
eu: {
|
||||
text: "EU",
|
||||
className: "swal-button--cancel",
|
||||
value: "eu"
|
||||
},
|
||||
other: {
|
||||
text: "Other",
|
||||
className: "swal-button--cancel",
|
||||
value: "other"
|
||||
}
|
||||
},
|
||||
dangerMode: false,
|
||||
}).then((region) => {
|
||||
handleRegion(region);
|
||||
})
|
||||
}
|
||||
|
||||
function handleRegion(region) {
|
||||
if(!region) {
|
||||
return;
|
||||
}
|
||||
let minAge = 16;
|
||||
if(['usa', 'uk', 'other'].includes(region)) {
|
||||
minAge = 13;
|
||||
}
|
||||
swal({
|
||||
title: "Enter Your Date of Birth",
|
||||
text: "We require your birthdate solely to confirm that you meet our age requirement.\n\n Rest assured, this information is not stored or used for any other purpose.",
|
||||
content: {
|
||||
element: "input",
|
||||
attributes: {
|
||||
placeholder: "Enter your birthday in YYYY-MM-DD format",
|
||||
type: "date",
|
||||
min: '1900-01-01',
|
||||
max: getToday(),
|
||||
pattern: "\d{4}-\d{2}-\d{2}",
|
||||
required: true
|
||||
}
|
||||
},
|
||||
buttons: {
|
||||
cancel: false,
|
||||
confirm: {
|
||||
text: 'Confirm Birthdate',
|
||||
className: "swal-button--cancel",
|
||||
}
|
||||
}
|
||||
}).then((inputValue) => {
|
||||
if (inputValue === false) return;
|
||||
|
||||
if (inputValue === "") {
|
||||
swal("Oops!", "You need to provide your date of birth to proceed!", "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
const dob = new Date(inputValue);
|
||||
if (isValidDate(dob)) {
|
||||
const age = calculateAge(dob);
|
||||
// swal(`Your age is ${age}`);
|
||||
if(age >= 120 || age <= 5) {
|
||||
swal({
|
||||
title: "Ineligible to join",
|
||||
text: "Sorry, the birth date you provided is not valid and we cannot process your request at this time.\n\nIf you entered your birthdate incorrectly you can try again, otherwise if you attempt to bypass our minimum age requirements, your account may be suspended.",
|
||||
icon: "error",
|
||||
buttons: {
|
||||
cancel: "I understand"
|
||||
}
|
||||
}).then(() => {
|
||||
window.location.href = '/'
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (age >= minAge) {
|
||||
document.getElementById('{{$id}}').submit();
|
||||
} else {
|
||||
swal({
|
||||
title: "Ineligible to join",
|
||||
text: `Sorry, you must be at least ${minAge} years old to join our service according to the laws of your country or region.`,
|
||||
icon: "error",
|
||||
buttons: {
|
||||
cancel: "I understand"
|
||||
}
|
||||
}).then(() => {
|
||||
window.location.href = '/'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
swal("Invalid date format!");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function calculateAge(dob) {
|
||||
const diff_ms = Date.now() - dob.getTime();
|
||||
console.log(diff_ms);
|
||||
const age_dt = new Date(diff_ms);
|
||||
return Math.abs(age_dt.getUTCFullYear() - 1970);
|
||||
}
|
||||
|
||||
function getToday() {
|
||||
var today = new Date();
|
||||
var dd = today.getDate();
|
||||
var mm = today.getMonth() + 1;
|
||||
var yyyy = today.getFullYear();
|
||||
|
||||
if (dd < 10) {
|
||||
dd = '0' + dd;
|
||||
}
|
||||
|
||||
if (mm < 10) {
|
||||
mm = '0' + mm;
|
||||
}
|
||||
|
||||
yyyy = yyyy - 10;
|
||||
|
||||
return yyyy + '-' + mm + '-' + dd;
|
||||
}
|
||||
|
||||
function isValidDate(d) {
|
||||
return d instanceof Date && !isNaN(d);
|
||||
}
|
||||
</script>
|
||||
@endpush
|
117
resources/views/auth/curated-register/partials/step-2.blade.php
Normal file
117
resources/views/auth/curated-register/partials/step-2.blade.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<h2><span style="opacity:0.5;">Let's begin setting up your account on</span> <strong style="opacity: 1">{{ config('pixelfed.domain.app') }}</strong></h2>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="step" value="2">
|
||||
<div class="my-5 details-form">
|
||||
<div class="details-form-field">
|
||||
<label class="text-muted small font-weight-bold mb-0">Username</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
placeholder="username"
|
||||
aria-label="Your username"
|
||||
aria-describedby="username-addon"
|
||||
maxlength="15"
|
||||
required
|
||||
name="username"
|
||||
value="{{ request()->session()->get('cur-reg.form-username') }}">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text bg-dark border-dark text-muted font-weight-bold" id="username-addon">@{{ config('pixelfed.domain.app') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="help-text small text-muted mb-0">You can use letters, numbers, and underscores with a max length of 15 chars.</p>
|
||||
</div>
|
||||
|
||||
<div class="details-form-field">
|
||||
<label class="text-muted small font-weight-bold mb-0">Email</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
placeholder="Your email address"
|
||||
name="email"
|
||||
value="{{ request()->session()->get('cur-reg.form-email') }}"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="details-form-field">
|
||||
<label class="text-muted small font-weight-bold mb-0">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
minlength="6"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
placeholder="Password"
|
||||
name="password"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<div class="details-form-field">
|
||||
<label class="text-muted small font-weight-bold mb-0">Password Confirm</label>
|
||||
<input
|
||||
type="password"
|
||||
autocomplete="new-password"
|
||||
minlength="6"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
placeholder="Confirm Password"
|
||||
name="password_confirmation"
|
||||
required>
|
||||
</div>
|
||||
<div class="border-top border-dark mt-3 pt-4">
|
||||
<p class="lead">
|
||||
Our moderators manually review sign-ups. To assist in the processing of your registration, please provide some information about yourself and explain why you wish to create an account on {{ config('pixelfed.domain.app') }}.
|
||||
</p>
|
||||
</div>
|
||||
<div class="details-form-field">
|
||||
<label class="text-muted small font-weight-bold mb-0">About yourself and why you'd like to join</label>
|
||||
<textarea
|
||||
class="form-control form-control-lg bg-dark text-white border-dark"
|
||||
rows="4"
|
||||
name="reason"
|
||||
maxlength="1000"
|
||||
id="reason"
|
||||
placeholder="Briefly explain why you'd like to join and optionally provide links to other accounts to help admins process your request"
|
||||
required>{{ request()->session()->get('cur-reg.form-reason') }}</textarea>
|
||||
<div class="help-text small text-muted float-right mt-1 font-weight-bold">
|
||||
<span id="charCount" class="text-white">0</span>/<span>1000</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-top border-dark mt-3 pt-4">
|
||||
<div class="details-form-field">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="agree" name="agree" required>
|
||||
<label class="custom-control-label text-muted" for="agree">I have read and agreed to our <a href="/site/terms" target="_blank" class="text-white">Terms of Use</a> and <a href="/site/privacy" target="_blank" class="text-white">Privacy Policy</a>.</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-4">
|
||||
<button class="btn btn-primary rounded-pill font-weight-bold btn-block">Proceed</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var textInput = document.getElementById('reason');
|
||||
var charCount = document.getElementById('charCount');
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
|
||||
textInput.addEventListener('input', function () {
|
||||
var currentLength = textInput.value.length;
|
||||
charCount.textContent = currentLength;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@push('styles')
|
||||
<style type="text/css">
|
||||
.details-form {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,30 @@
|
|||
<h2><span style="opacity:0.5;">Confirm Your Email</h2>
|
||||
@if(isset($verifiedEmail))
|
||||
<div class="alert alert-success bg-success border-success p-4 text-center mt-5">
|
||||
<p class="text-center text-white mb-4"><i class="far fa-envelope-open fa-4x"></i></p>
|
||||
<p class="lead font-weight-bold text-white mb-0">Please check your email inbox, we sent an email confirmation with a link that you need to visit.</p>
|
||||
</div>
|
||||
@else
|
||||
<p class="lead">Please confirm your email address is correct, we will send a verification e-mail with a special verification link that you need to visit before proceeding.</p>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<input type="hidden" name="step" value="3">
|
||||
<div class="details-form-field">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
placeholder="Your email address"
|
||||
name="email"
|
||||
value="{{ request()->session()->get('cur-reg.form-email') }}"
|
||||
required>
|
||||
</div>
|
||||
@if(config('instance.curated_registration.captcha_enabled'))
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
@endif
|
||||
<div class="mt-3 pt-4">
|
||||
<button class="btn btn-primary rounded-pill font-weight-bold btn-block">My email is correct</button>
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
|
@ -0,0 +1,2 @@
|
|||
<h2><span>Processing your membership request</span></h2>
|
||||
<p class="lead">We will send you an email once your account is ready!</p>
|
|
@ -0,0 +1,112 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 3])
|
||||
|
||||
@if ($errors->any())
|
||||
@foreach ($errors->all() as $error)
|
||||
<div class="alert alert-danger bg-danger border-danger text-white">
|
||||
<p class="lead font-weight-bold mb-0"><i class="far fa-exclamation-triangle mr-2"></i> {!! $error !!}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
<div class="mb-5"></div>
|
||||
@endif
|
||||
|
||||
<h1 class="text-center">Resend Confirmation</h1>
|
||||
<p class="lead text-center">Please confirm your email address so we verify your registration application to re-send your email verification email.</p>
|
||||
|
||||
<form method="post">
|
||||
@csrf
|
||||
<div class="form-group">
|
||||
<input
|
||||
type="email"
|
||||
class="form-control form-control-lg bg-dark border-dark text-white"
|
||||
name="email"
|
||||
placeholder="Your email address"
|
||||
required />
|
||||
</div>
|
||||
@if(config('instance.curated_registration.captcha_enabled'))
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
@endif
|
||||
<div class="d-flex justify-content-center">
|
||||
<button class="btn btn-primary font-weight-bold rounded-pill px-5">Verify</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div class="col-12 col-md-7 mt-5">
|
||||
<div class="small-links">
|
||||
<a href="/">Home</a>
|
||||
<span>·</span>
|
||||
<a href="/login">Login</a>
|
||||
<span>·</span>
|
||||
<a href="{{route('help.curated-onboarding')}}" target="_blank">Help</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,87 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 3])
|
||||
|
||||
<div class="alert alert-muted bg-transparent border-muted p-4 text-center mt-5">
|
||||
<p class="text-center text-success mb-4"><i class="far fa-envelope-open fa-4x"></i></p>
|
||||
<p class="h2 font-weight-bold text-white">Please check your email inbox</p>
|
||||
<hr style="opacity: 0.2">
|
||||
<p class="lead text-white">We sent a confirmation link to your email that you need to verify before we can process your registration application.</p>
|
||||
<p class="text-muted mb-0">The verification link expires after 24 hours.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-7 mt-5">
|
||||
<div class="small-links">
|
||||
<a href="/">Home</a>
|
||||
<span>·</span>
|
||||
<a href="/login">Login</a>
|
||||
<span>·</span>
|
||||
<a href="{{route('help.curated-onboarding')}}" target="_blank">Help</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -0,0 +1,79 @@
|
|||
@extends('layouts.blank')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center">
|
||||
<div class="col-12 col-md-7">
|
||||
<div class="logo">
|
||||
<img src="/img/pixelfed-icon-color.svg" width="40" height="40" alt="Pixelfed Logo">
|
||||
<p class="font-weight-bold mb-0">Pixelfed</p>
|
||||
</div>
|
||||
|
||||
@include('auth.curated-register.partials.progress-bar', ['step' => 4])
|
||||
|
||||
<p class="text-center"><i class="fal fa-check-circle text-success fa-8x"></i></p>
|
||||
<h1 class="text-center font-weight-bold my-4">Succesfully Sent Response!</h1>
|
||||
<p class="h4 text-center"><span class="font-weight-bold">Our admin team will review your application.</span></p>
|
||||
<hr class="border-dark">
|
||||
<p class="lead text-center">Most applications are processed within 24-48 hours. We will send you an email once your account is ready!</p>
|
||||
<p class="text-muted text-center">If we need any additional information, we will send you an automated request with a link that you can visit and provide further details to help process your application.</p>
|
||||
<hr class="border-dark">
|
||||
<p class="text-muted small text-center">For additional information, please see our <a href="{{ route('help.curated-onboarding') }}" style="font-weight: 600;">Curated Onboarding</a> Help Center page.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('styles')
|
||||
<link href="{{ mix('css/landing.css') }}" rel="stylesheet">
|
||||
<style type="text/css">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.opacity-5 {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
margin-top: 50px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
|
||||
p {
|
||||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
display: flex;
|
||||
margin: 3rem auto 1rem auto;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
|
||||
form {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.small-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-bottom: 5rem;
|
||||
gap: 1rem;
|
||||
|
||||
a {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endpush
|
|
@ -111,7 +111,7 @@
|
|||
</form>
|
||||
@endif
|
||||
|
||||
@if(config_cache('pixelfed.open_registration'))
|
||||
@if((bool) config_cache('pixelfed.open_registration') || (bool) config_cache('instance.curated_registration.enabled'))
|
||||
<hr>
|
||||
|
||||
<p class="text-center font-weight-bold mb-0">
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
@component('mail::message')
|
||||
# [#{{$verify->id}}] New Curated Onboarding Application
|
||||
|
||||
Hello admin,
|
||||
|
||||
**Please review this new onboarding application.**
|
||||
|
||||
<x-mail::panel>
|
||||
<p>
|
||||
<small>
|
||||
Username: <strong>{{ $verify->username }}</strong>
|
||||
</small>
|
||||
<br>
|
||||
<small>
|
||||
Email: <strong>{{ $verify->email }}</strong>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<small><strong>*The user provided the following reason to join:*</strong></small>
|
||||
<p style="font-size:9pt;">{!!str_limit(nl2br($verify->reason_to_join), 300)!!}</p>
|
||||
</x-mail::panel>
|
||||
|
||||
<x-mail::button :url="$verify->adminReviewUrl()" color="success">
|
||||
<strong>Review Onboarding Application</strong>
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -0,0 +1,21 @@
|
|||
@component('mail::message')
|
||||
# New Curated Onboarding Response ({{ '#' . $activity->id}})
|
||||
|
||||
Hello,
|
||||
|
||||
You have a new response from a curated onboarding application from **{{$activity->application->email}}**.
|
||||
|
||||
<x-mail::panel>
|
||||
<p style="white-space: pre-wrap;">{!! $activity->message !!}</p>
|
||||
</x-mail::panel>
|
||||
|
||||
<x-mail::button :url="$activity->adminReviewUrl()" color="success">
|
||||
<strong>Review Onboarding Response</strong>
|
||||
</x-mail::button>
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -0,0 +1,21 @@
|
|||
@component('mail::message')
|
||||
# Action Needed: Confirm Your Email to Activate Your Pixelfed Account
|
||||
|
||||
Hello **{{'@'.$verify->username}}**,
|
||||
|
||||
Please confirm your email address so we can process your new registration application.
|
||||
|
||||
<x-mail::button :url="$verify->emailConfirmUrl()" color="success">
|
||||
<strong>Confirm Email Address</strong>
|
||||
</x-mail::button>
|
||||
|
||||
|
||||
<p style="font-size:10pt;">If you did not create this account, please disregard this email. This link expires after 24 hours.</p>
|
||||
<br>
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -0,0 +1,21 @@
|
|||
@component('mail::message')
|
||||
# New Message from {{config('pixelfed.domain.app')}}
|
||||
|
||||
Hello,
|
||||
|
||||
You recently applied to join our Pixelfed community using the @**{{ $verify->username }}** username.
|
||||
|
||||
The admins have a message for you:
|
||||
|
||||
<x-mail::panel>
|
||||
<p style="white-space: pre-wrap;">{{ $verify->message }}</p>
|
||||
</x-mail::panel>
|
||||
|
||||
Please do not respond to this email, any replies will not be seen by our admin team.
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message on behalf of our admin team, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -0,0 +1,37 @@
|
|||
@component('mail::message')
|
||||
Hello **{{'@'.$verify->username}}**,
|
||||
|
||||
|
||||
We are excited to inform you that your account has been successfully activated!
|
||||
|
||||
Your journey into the world of visual storytelling begins now, and we can’t wait to see the incredible content you’ll create and share.
|
||||
|
||||
<x-mail::button :url="url('/login?email=' . $verify->email)" color="success">
|
||||
<strong>Sign-in to your account</strong>
|
||||
</x-mail::button>
|
||||
|
||||
Here’s what you can do next:
|
||||
|
||||
<x-mail::panel>
|
||||
**Personalize Your Profile**: Customize your profile to reflect your personality or brand.
|
||||
|
||||
**Start Sharing**: Post your first photo or album and share your unique perspective with the world.
|
||||
|
||||
**Engage with the Community**: Follow other users, like and comment on posts, and become an active member of our vibrant community.
|
||||
|
||||
**Explore**: Discover amazing content from a diverse range of users and hashtags.
|
||||
</x-mail::panel>
|
||||
|
||||
Need help getting started? Visit our [Help Center]({{url('site/help')}}) for tips, tutorials, and FAQs. Remember, our community thrives on respect and creativity, so please familiarize yourself with our [Community Guidelines]({{url('site/kb/community-guidelines')}}).
|
||||
|
||||
If you have any questions or need assistance, feel free to reach out to [our support team]({{url('/site/contact')}}).
|
||||
|
||||
Happy posting, and once again, welcome to Pixelfed!
|
||||
|
||||
Warm regards,<br>
|
||||
<strong>{{ config('pixelfed.domain.app') }}</strong>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -0,0 +1,22 @@
|
|||
@component('mail::message')
|
||||
# Action Needed: Additional information requested
|
||||
|
||||
Hello **{{'@'.$verify->username}}**
|
||||
|
||||
To help us process your registration application, we require more information.
|
||||
|
||||
Our onboarding team have requested the following details:
|
||||
|
||||
@component('mail::panel')
|
||||
<p style="white-space: pre-wrap;">{!! $activity->message !!}</p>
|
||||
@endcomponent
|
||||
<x-mail::button :url="$activity->emailReplyUrl()" color="success">
|
||||
<strong>Reply with your response</strong>
|
||||
</x-mail::button>
|
||||
|
||||
<p style="font-size:10pt;">Please respond promptly, your application will be automatically removed 7 days after your last interaction.</p>
|
||||
<br>
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
@endcomponent
|
|
@ -0,0 +1,19 @@
|
|||
@component('mail::message')
|
||||
Hello **{{'@'.$verify->username}}**,
|
||||
|
||||
We appreciate the time you took to apply for an account on {{ config('pixelfed.domain.app') }}.
|
||||
|
||||
Unfortunately, after reviewing your [application]({{route('help.curated-onboarding')}}), we have decided not to proceed with the activation of your account.
|
||||
|
||||
This decision is made to ensure the best experience for all members of our community. We encourage you to review our [guidelines]({{route('help.community-guidelines')}}) and consider applying again in the future.
|
||||
|
||||
We appreciate your understanding. If you believe this decision was made in error, or if you have any questions, please don’t hesitate to [contact us]({{route('site.contact')}}).
|
||||
|
||||
<br>
|
||||
|
||||
Thanks,<br>
|
||||
<a href="{{ config('app.url') }}">{{ config('pixelfed.domain.app') }}</a>
|
||||
<br>
|
||||
<hr>
|
||||
<p style="font-size:10pt;">This is an automated message, please be aware that replies to this email cannot be monitored or responded to.</p>
|
||||
@endcomponent
|
|
@ -208,7 +208,9 @@
|
|||
swal.stopLoading()
|
||||
swal.close()
|
||||
this.index = 0
|
||||
this.blocks.unshift(parsedUrl.hostname)
|
||||
if(this.blocks.indexOf(parsedUrl.hostname) === -1) {
|
||||
this.blocks.unshift(parsedUrl.hostname)
|
||||
}
|
||||
this.buildList()
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
162
resources/views/site/help/curated-onboarding.blade.php
Normal file
162
resources/views/site/help/curated-onboarding.blade.php
Normal file
|
@ -0,0 +1,162 @@
|
|||
@extends('site.help.partial.template', ['breadcrumb'=>'Curated Onboarding'])
|
||||
|
||||
@section('section')
|
||||
<div class="title">
|
||||
<h3 class="font-weight-bold">Curated Onboarding</h3>
|
||||
</div>
|
||||
<hr>
|
||||
@if((bool) config_cache('instance.curated_registration.enabled') == false)
|
||||
<div class="card bg-danger mb-3">
|
||||
<div class="card-body">
|
||||
@if((bool) config_cache('pixelfed.open_registration'))
|
||||
<p class="mb-0 text-white font-weight-bold">Curated Onboarding is not available on this server, however anyone can join.</p>
|
||||
<hr>
|
||||
<p class="mb-0 text-center"><a href="/register" class="btn btn-light font-weight-bold rounded-pill">Create New Account</a></p>
|
||||
@else
|
||||
<p class="mb-0 text-white font-weight-bold">Curated Onboarding is not available on this server.</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<p class="lead font-weight-bold">Curated Onboarding is our innovative approach to ensure each new member is a perfect fit for our community.</p>
|
||||
<p class="lead font-weight-light">This process goes beyond the usual sign-up routine. It's a thoughtful method to understand each applicant's intentions and aspirations within our platform.</p>
|
||||
<p class="lead font-weight-light">If you're excited to be a part of a platform that values individuality, creativity, and community, we invite you to apply to join our community. Share with us your story, and let's embark on this visual journey together!</p>
|
||||
@if((bool) config_cache('instance.curated_registration.enabled') && !request()->user())
|
||||
<p class="text-center pt-3">
|
||||
<a class="btn btn-outline-primary rounded-pill btn-lg font-weight-bold px-5" href="{{ route('auth.curated-onboarding') }}">Apply to Join <i class="far fa-arrow-right ml-2"></i></a>
|
||||
</p>
|
||||
@endif
|
||||
<hr class="my-5">
|
||||
<h5 class="text-center text-muted font-weight-light my-5">How does Curated Onboarding work?</h5>
|
||||
<ol>
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Application Submission</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">Start your journey by providing your username and email, along with a personal note about why you're excited to join Pixelfed. This insight into your interests and aspirations helps us get to know you better.</p>
|
||||
</li>
|
||||
<hr class="my-5">
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Admin Review and Interaction</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">Our team carefully reviews each application, assessing your fit within our community. If we're intrigued but need more information, we'll reach out directly. You'll receive an email with a link to a special form where you can view our request and respond in detail. This two-way communication ensures a thorough and fair evaluation process.</p>
|
||||
</li>
|
||||
<hr class="my-5">
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Decision – Acceptance or Rejection</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">Each application is thoughtfully considered. If you're a match for our community, you'll be greeted with a warm welcome email and instructions to activate your account. If your application is not accepted, we will inform you respectfully, leaving the possibility open for future applications.</p>
|
||||
</li>
|
||||
</ol>
|
||||
<hr class="my-5">
|
||||
<h5 class="text-center text-muted font-weight-light my-5">Why Curated Onboarding?</h5>
|
||||
<ul>
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Fostering Quality Connections</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">At Pixelfed, we believe in the power of meaningful connections. It's not just about how many people are in the community, but how they enrich and enliven our platform. Our curated onboarding process is designed to welcome members who share our enthusiasm for creativity and engagement, ensuring every interaction on Pixelfed is enjoyable and rewarding.</p>
|
||||
</li>
|
||||
<hr class="my-5">
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Ensuring Safety and Respect</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">A careful onboarding process is critical for maintaining a safe and respectful environment. It allows Pixelfed to align every new member with the platform's values of kindness and inclusivity.</p>
|
||||
</li>
|
||||
<hr class="my-5">
|
||||
<li>
|
||||
<p class="h5 font-weight-bold mb-2">Encouraging Community Engagement</p>
|
||||
<p style="font-size: 16px;line-height: 1.7;">By engaging with applicants from the start, Pixelfed fosters a community that's not just active but passionate. This approach welcomes users who are genuinely interested in making a positive contribution to Pixelfed's vibrant community.</p>
|
||||
</li>
|
||||
</ul>
|
||||
<hr class="my-5">
|
||||
<h5 class="text-center text-muted font-weight-light my-5">FAQs & Troubleshooting</h5>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse1" role="button" aria-expanded="false" aria-controls="collapse1">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
"You have re-attempted too many times."
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse1">
|
||||
<div>
|
||||
This indicates that you've attempted to verify your email address too many times. This most likely is the result of an issue delivering the verification emails to your email provider. If you are experiencing this issue, we suggest that you <a href="/site/contact">contact the admin onboarding team</a> and mention that you're having issues verifying your email address.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse2" role="button" aria-expanded="false" aria-controls="collapse2">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
I haven't recieved the email confirmation
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse2">
|
||||
<div>
|
||||
This indicates the desired username is already in-use or was previously used by a now deleted account. You need to pick a different username.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse3" role="button" aria-expanded="false" aria-controls="collapse3">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
"Email is invalid."
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse3">
|
||||
<div>
|
||||
This indicates the desired email is not supported. While it may be a valid email, admins may have blocked specific domains from being associated with account email addresses.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse4" role="button" aria-expanded="false" aria-controls="collapse4">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
"The username has already been taken."
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse4">
|
||||
<div>
|
||||
This indicates the desired username is already in-use or was previously used by a now deleted account. You need to pick a different username.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse5" role="button" aria-expanded="false" aria-controls="collapse5">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
"Username is invalid. Can only contain one dash (-), period (.) or underscore (_)."
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse5">
|
||||
<div>
|
||||
This indicates the desired username is not a valid format, usernames may only contain one dash (-), period (.) or underscore (_) and must start with a letter or number.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse6" role="button" aria-expanded="false" aria-controls="collapse6">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
"The reason must be at least 20 characters."
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse6">
|
||||
<div>
|
||||
This indicates the reason you provided for joining is less than the minimum accepted characters. The reason should be atleast 20 characters long, up to 1000 characters. If you desire to share a longer reason than 1000 characters, consider using a pastebin and posting the link. We can't guarantee that admins will visit any links you provided, so ideally you can keep the length within 1000 chars.
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<a class="text-dark font-weight-bold" data-toggle="collapse" href="#collapse7" role="button" aria-expanded="false" aria-controls="collapse7">
|
||||
<i class="fas fa-chevron-down mr-2"></i>
|
||||
My application was rejected, what can I do now?
|
||||
</a>
|
||||
<div class="collapse mb-5" id="collapse7">
|
||||
<div>
|
||||
<p>We understand that receiving a notification of rejection can be disappointing. Here's what you can consider if your application to join Pixelfed hasn't been successful:</p>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
<strong>Review Your Application:</strong> Reflect on the information you provided. Our decision may have been influenced by a variety of factors, including the clarity of your intentions or how well they align with our community values. Consider if there was any additional context or passion for Pixelfed that you could have included.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Reapply with Updated Information:</strong> We encourage you to reapply if you feel your initial application didn’t fully capture your enthusiasm or alignment with our community values. However, we recommend exercising caution and thoughtfulness. Please take time to refine and enhance your application before resubmitting, as repetitive or frequent submissions can overwhelm our admin team. We value careful consideration and meaningful updates in reapplications, as this helps us maintain a fair and manageable review process for everyone. Your patience and understanding in this regard are greatly appreciated and can positively influence the outcome of future applications.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Seek Feedback:</strong> If you are seeking clarity on why your application wasn't successful, you're welcome to contact us for feedback. However, please be mindful that our admins handle a high volume of queries and applications. While we strive to provide helpful responses, our ability to offer detailed individual feedback may be limited. We ask for your patience and understanding in this matter. When reaching out, ensure your query is concise and considerate of the admins' time. This approach will help us assist you more effectively and maintain a positive interaction, even if your initial application didn't meet our criteria.
|
||||
</li>
|
||||
<li>
|
||||
<strong>Stay Engaged:</strong> Even if you're not a member yet, you can stay connected with Pixelfed through our public forums, blog, or social media channels. This will keep you updated on any changes or new features that might make our platform a better fit for you in the future.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Remember, a rejection is not necessarily a reflection of your qualities or potential as a member of our community. It's often about finding the right fit at the right time. We appreciate your interest in Pixelfed and hope you won't be discouraged from exploring other ways to engage with our platform.</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
@endsection
|
|
@ -33,6 +33,13 @@
|
|||
<li class="nav-item {{request()->is('*/import')?'active':''}}">
|
||||
<a class="nav-link font-weight-light text-muted" href="{{route('help.import')}}">Instagram Import</a>
|
||||
</li>
|
||||
@if((bool) config_cache('instance.curated_registration.enabled'))
|
||||
<li class="nav-item {{request()->is('*/curated-onboarding')?'active':''}}">
|
||||
<a class="nav-link font-weight-light text-muted" href="{{route('help.curated-onboarding')}}">
|
||||
Curated Onboarding
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
<li class="nav-item">
|
||||
<hr>
|
||||
</li>
|
||||
|
|
|
@ -104,11 +104,11 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
Route::post('asf/create', 'AdminShadowFilterController@store');
|
||||
|
||||
Route::get('asf/home', 'AdminShadowFilterController@home');
|
||||
// Route::redirect('curated-onboarding/', 'curated-onboarding/home');
|
||||
// Route::get('curated-onboarding/home', 'AdminCuratedRegisterController@index')->name('admin.curated-onboarding');
|
||||
// Route::get('curated-onboarding/show/{id}/preview-details-message', 'AdminCuratedRegisterController@previewDetailsMessageShow');
|
||||
// Route::get('curated-onboarding/show/{id}/preview-message', 'AdminCuratedRegisterController@previewMessageShow');
|
||||
// Route::get('curated-onboarding/show/{id}', 'AdminCuratedRegisterController@show');
|
||||
Route::redirect('curated-onboarding/', 'curated-onboarding/home');
|
||||
Route::get('curated-onboarding/home', 'AdminCuratedRegisterController@index')->name('admin.curated-onboarding');
|
||||
Route::get('curated-onboarding/show/{id}/preview-details-message', 'AdminCuratedRegisterController@previewDetailsMessageShow');
|
||||
Route::get('curated-onboarding/show/{id}/preview-message', 'AdminCuratedRegisterController@previewMessageShow');
|
||||
Route::get('curated-onboarding/show/{id}', 'AdminCuratedRegisterController@show');
|
||||
|
||||
Route::prefix('api')->group(function() {
|
||||
Route::get('stats', 'AdminController@getStats');
|
||||
|
@ -157,10 +157,10 @@ Route::domain(config('pixelfed.domain.admin'))->prefix('i/admin')->group(functio
|
|||
Route::post('autospam/config/enable', 'AdminController@enableAutospamApi');
|
||||
Route::post('autospam/config/disable', 'AdminController@disableAutospamApi');
|
||||
// Route::get('instances/{id}/accounts', 'AdminController@getInstanceAccounts');
|
||||
// Route::get('curated-onboarding/show/{id}/activity-log', 'AdminCuratedRegisterController@apiActivityLog');
|
||||
// Route::post('curated-onboarding/show/{id}/message/preview', 'AdminCuratedRegisterController@apiMessagePreviewStore');
|
||||
// Route::post('curated-onboarding/show/{id}/message/send', 'AdminCuratedRegisterController@apiMessageSendStore');
|
||||
// Route::post('curated-onboarding/show/{id}/reject', 'AdminCuratedRegisterController@apiHandleReject');
|
||||
// Route::post('curated-onboarding/show/{id}/approve', 'AdminCuratedRegisterController@apiHandleApprove');
|
||||
Route::get('curated-onboarding/show/{id}/activity-log', 'AdminCuratedRegisterController@apiActivityLog');
|
||||
Route::post('curated-onboarding/show/{id}/message/preview', 'AdminCuratedRegisterController@apiMessagePreviewStore');
|
||||
Route::post('curated-onboarding/show/{id}/message/send', 'AdminCuratedRegisterController@apiMessageSendStore');
|
||||
Route::post('curated-onboarding/show/{id}/reject', 'AdminCuratedRegisterController@apiHandleReject');
|
||||
Route::post('curated-onboarding/show/{id}/approve', 'AdminCuratedRegisterController@apiHandleApprove');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,16 +29,18 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::get('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegister');
|
||||
Route::post('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegisterStore');
|
||||
|
||||
// Route::get('auth/sign_up', 'CuratedRegisterController@index');
|
||||
// Route::post('auth/sign_up', 'CuratedRegisterController@proceed');
|
||||
// Route::get('auth/sign_up/concierge/response-sent', 'CuratedRegisterController@conciergeResponseSent');
|
||||
// Route::get('auth/sign_up/concierge', 'CuratedRegisterController@concierge');
|
||||
// Route::post('auth/sign_up/concierge', 'CuratedRegisterController@conciergeStore');
|
||||
// Route::get('auth/sign_up/concierge/form', 'CuratedRegisterController@conciergeFormShow');
|
||||
// Route::post('auth/sign_up/concierge/form', 'CuratedRegisterController@conciergeFormStore');
|
||||
// Route::get('auth/sign_up/confirm', 'CuratedRegisterController@confirmEmail');
|
||||
// Route::post('auth/sign_up/confirm', 'CuratedRegisterController@confirmEmailHandle');
|
||||
// Route::get('auth/sign_up/confirmed', 'CuratedRegisterController@emailConfirmed');
|
||||
Route::get('auth/sign_up', 'CuratedRegisterController@index')->name('auth.curated-onboarding');
|
||||
Route::post('auth/sign_up', 'CuratedRegisterController@proceed');
|
||||
Route::get('auth/sign_up/concierge/response-sent', 'CuratedRegisterController@conciergeResponseSent');
|
||||
Route::get('auth/sign_up/concierge', 'CuratedRegisterController@concierge');
|
||||
Route::post('auth/sign_up/concierge', 'CuratedRegisterController@conciergeStore');
|
||||
Route::get('auth/sign_up/concierge/form', 'CuratedRegisterController@conciergeFormShow');
|
||||
Route::post('auth/sign_up/concierge/form', 'CuratedRegisterController@conciergeFormStore');
|
||||
Route::get('auth/sign_up/confirm', 'CuratedRegisterController@confirmEmail');
|
||||
Route::post('auth/sign_up/confirm', 'CuratedRegisterController@confirmEmailHandle');
|
||||
Route::get('auth/sign_up/confirmed', 'CuratedRegisterController@emailConfirmed');
|
||||
Route::get('auth/sign_up/resend-confirmation', 'CuratedRegisterController@resendConfirmation');
|
||||
Route::post('auth/sign_up/resend-confirmation', 'CuratedRegisterController@resendConfirmationProcess');
|
||||
Route::get('auth/forgot/email', 'UserEmailForgotController@index')->name('email.forgot');
|
||||
Route::post('auth/forgot/email', 'UserEmailForgotController@store')->middleware('throttle:10,900,forgotEmail');
|
||||
|
||||
|
@ -306,7 +308,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
|
|||
Route::view('import', 'site.help.import')->name('help.import');
|
||||
Route::view('parental-controls', 'site.help.parental-controls');
|
||||
// Route::view('email-confirmation-issues', 'site.help.email-confirmation-issues')->name('help.email-confirmation-issues');
|
||||
// Route::view('curated-onboarding', 'site.help.curated-onboarding')->name('help.curated-onboarding');
|
||||
Route::view('curated-onboarding', 'site.help.curated-onboarding')->name('help.curated-onboarding');
|
||||
});
|
||||
Route::get('newsroom/{year}/{month}/{slug}', 'NewsroomController@show');
|
||||
Route::get('newsroom/archive', 'NewsroomController@archive');
|
||||
|
|
Loading…
Reference in a new issue