diff --git a/app/Helpers/ComplianceHelper.php b/app/Helpers/ComplianceHelper.php
new file mode 100644
index 000000000..6932f1391
--- /dev/null
+++ b/app/Helpers/ComplianceHelper.php
@@ -0,0 +1,45 @@
+where('user_id', $user->id)
+ ->where('account_id', $user->account_id)
+ ->where('term_id', $term->id)
+ ->first();
+
+ if (! $termUser) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Indicate if the user has accepted the most recent terms and privacy.
+ * This really is a shortcut of the `hasSignedGivenTerm` method.
+ *
+ * @param User $user
+ * @return bool
+ */
+ public static function isCompliantWithCurrentTerm(User $user): bool
+ {
+ $latestTerm = Term::latest()->first();
+
+ return self::hasSignedGivenTerm($user, $latestTerm);
+ }
+}
diff --git a/app/Helpers/FormHelper.php b/app/Helpers/FormHelper.php
new file mode 100644
index 000000000..b7f1f5343
--- /dev/null
+++ b/app/Helpers/FormHelper.php
@@ -0,0 +1,36 @@
+name_order) {
+ case 'firstname_lastname':
+ case 'firstname_lastname_nickname':
+ case 'firstname_nickname_lastname':
+ case 'nickname':
+ $nameOrder = 'firstname';
+ break;
+ case 'lastname_firstname':
+ case 'lastname_firstname_nickname':
+ case 'lastname_nickname_firstname':
+ $nameOrder = 'lastname';
+ break;
+ }
+
+ return $nameOrder;
+ }
+}
diff --git a/app/Helpers/JournalHelper.php b/app/Helpers/JournalHelper.php
new file mode 100644
index 000000000..607081f30
--- /dev/null
+++ b/app/Helpers/JournalHelper.php
@@ -0,0 +1,29 @@
+account_id)
+ ->where('date', now($user->timezone)->toDateString())
+ ->firstOrFail();
+ } catch (ModelNotFoundException $e) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/Http/Controllers/Api/Account/ApiUserController.php b/app/Http/Controllers/Api/Account/ApiUserController.php
index e33a3bc5a..fa2356f99 100644
--- a/app/Http/Controllers/Api/Account/ApiUserController.php
+++ b/app/Http/Controllers/Api/Account/ApiUserController.php
@@ -3,12 +3,18 @@
namespace App\Http\Controllers\Api\Account;
use App\Models\User\User;
+use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use App\Models\Settings\Term;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Support\Facades\DB;
+use App\Services\User\AcceptPolicy;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Api\ApiController;
+use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Account\User\User as UserResource;
+use App\Http\Resources\Settings\Compliance\Compliance as ComplianceResource;
class ApiUserController extends ApiController
{
@@ -30,18 +36,35 @@ class ApiUserController extends ApiController
* @param Request $request
* @param int $termId
*
- * @return \Illuminate\Http\JsonResponse
+ * @return JsonResponse
*/
public function get(Request $request, $termId)
{
- $userCompliance = auth()->user()->getStatusForCompliance($termId);
+ try {
+ $term = Term::findOrFail($termId);
+ } catch (ModelNotFoundException $e) {
+ return $this->respondNotFound();
+ }
- if (! $userCompliance) {
+ $termUser = DB::table('term_user')->where('user_id', auth()->user()->id)
+ ->where('account_id', auth()->user()->account_id)
+ ->where('term_id', $term->id)
+ ->first();
+
+ if ($termUser) {
+ $data = [
+ 'signed' => true,
+ 'signed_date' => DateHelper::getTimestamp($termUser->created_at),
+ 'ip_address' => $termUser->ip_address,
+ 'user' => new UserResource(auth()->user()),
+ 'term' => new ComplianceResource($term),
+ ];
+ } else {
return $this->respondNotFound();
}
return $this->respond([
- 'data' => $userCompliance,
+ 'data' => $data,
]);
}
@@ -50,11 +73,30 @@ class ApiUserController extends ApiController
*
* @param Request $request
*
- * @return \Illuminate\Http\JsonResponse
+ * @return JsonResponse
*/
- public function compliance(Request $request)
+ public function getSignedPolicies(Request $request)
{
- $terms = auth()->user()->getAllCompliances();
+ $terms = collect();
+ $termsForUser = DB::table('term_user')
+ ->where('user_id', auth()->user()->id)
+ ->get();
+
+ if ($termsForUser->count() == 0) {
+ return $this->respondNotFound();
+ }
+
+ foreach ($termsForUser as $termUser) {
+ $term = Term::findOrFail($termUser->term_id);
+
+ $terms->push([
+ 'signed' => true,
+ 'signed_date' => DateHelper::getTimestamp($termUser->created_at),
+ 'ip_address' => $termUser->ip_address,
+ 'user' => new UserResource(auth()->user()),
+ 'term' => new ComplianceResource($term),
+ ]);
+ }
return $this->respond([
'data' => $terms,
@@ -66,7 +108,7 @@ class ApiUserController extends ApiController
*
* @param Request $request
*
- * @return \Illuminate\Http\JsonResponse
+ * @return JsonResponse
*/
public function set(Request $request)
{
@@ -78,17 +120,33 @@ class ApiUserController extends ApiController
return $this->respondValidatorFailed($validator);
}
- // Create the contact
try {
- $term = auth()->user()->acceptPolicy($request->input('ip_address'));
+ $term = app(AcceptPolicy::class)->execute([
+ 'account_id' => auth()->user()->account->id,
+ 'user_id' => auth()->user()->id,
+ 'ip_address' => $request->input('ip_address'),
+ ]);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
- $userCompliance = auth()->user()->getStatusForCompliance($term->id);
+ try {
+ $termUser = DB::table('term_user')->where('user_id', auth()->user()->id)
+ ->where('account_id', auth()->user()->account_id)
+ ->where('term_id', $term->id)
+ ->first();
+ } catch (ModelNotFoundException $e) {
+ return $this->respondInvalidQuery();
+ }
return $this->respond([
- 'data' => $userCompliance,
+ 'data' => [
+ 'signed' => true,
+ 'signed_date' => DateHelper::getTimestamp($termUser->created_at),
+ 'ip_address' => $termUser->ip_address,
+ 'user' => new UserResource(auth()->user()),
+ 'term' => new ComplianceResource($term),
+ ],
]);
}
}
diff --git a/app/Http/Controllers/ComplianceController.php b/app/Http/Controllers/ComplianceController.php
index 27f084450..a575698ab 100644
--- a/app/Http/Controllers/ComplianceController.php
+++ b/app/Http/Controllers/ComplianceController.php
@@ -2,7 +2,11 @@
namespace App\Http\Controllers;
+use Illuminate\View\View;
use Illuminate\Http\Request;
+use App\Services\User\AcceptPolicy;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Contracts\View\Factory;
class ComplianceController extends Controller
{
@@ -11,7 +15,7 @@ class ComplianceController extends Controller
*
* @param Request $request
*
- * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
+ * @return View|Factory
*/
public function index(Request $request)
{
@@ -19,13 +23,17 @@ class ComplianceController extends Controller
}
/**
- * @param \Illuminate\Http\Request $request
+ * @param Request $request
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
*/
public function store(Request $request)
{
- auth()->user()->acceptPolicy(\Request::ip());
+ app(AcceptPolicy::class)->execute([
+ 'account_id' => auth()->user()->account->id,
+ 'user_id' => auth()->user()->id,
+ 'ip_address' => \Request::ip(),
+ ]);
return redirect()->route('dashboard.index');
}
diff --git a/app/Http/Controllers/Contacts/RelationshipsController.php b/app/Http/Controllers/Contacts/RelationshipsController.php
index 6169476d0..55ddf3dcd 100644
--- a/app/Http/Controllers/Contacts/RelationshipsController.php
+++ b/app/Http/Controllers/Contacts/RelationshipsController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
+use App\Helpers\FormHelper;
use Illuminate\Http\Request;
use App\Helpers\GendersHelper;
use App\Models\Contact\Contact;
@@ -41,7 +42,8 @@ class RelationshipsController extends Controller
->withMonths(DateHelper::getListOfMonths())
->withBirthdate(now(DateHelper::getTimezone())->toDateString())
->withExistingContacts(ContactResource::collection($existingContacts))
- ->withType($request->input('type'));
+ ->withType($request->input('type'))
+ ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
@@ -118,7 +120,8 @@ class RelationshipsController extends Controller
->withMonth($month)
->withAge($age)
->withGenders(GendersHelper::getGendersInput())
- ->withHasBirthdayReminder($hasBirthdayReminder);
+ ->withHasBirthdayReminder($hasBirthdayReminder)
+ ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
diff --git a/app/Http/Controllers/ContactsController.php b/app/Http/Controllers/ContactsController.php
index b7433bf9a..cb02398d8 100644
--- a/app/Http/Controllers/ContactsController.php
+++ b/app/Http/Controllers/ContactsController.php
@@ -3,7 +3,9 @@
namespace App\Http\Controllers;
use App\Helpers\DBHelper;
+use Illuminate\View\View;
use App\Helpers\DateHelper;
+use App\Helpers\FormHelper;
use App\Models\Contact\Tag;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
@@ -14,8 +16,11 @@ use App\Models\Contact\Contact;
use App\Services\VCard\ExportVCard;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
+use Illuminate\Http\RedirectResponse;
+use Illuminate\Contracts\View\Factory;
use App\Models\Relationship\Relationship;
use Barryvdh\Debugbar\Facade as Debugbar;
+use App\Services\User\UpdateViewPreference;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Contact\CreateContact;
use App\Services\Contact\Contact\UpdateContact;
@@ -31,7 +36,7 @@ class ContactsController extends Controller
*
* @param Request $request
*
- * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
+ * @return View|RedirectResponse
*/
public function index(Request $request)
{
@@ -43,7 +48,7 @@ class ContactsController extends Controller
*
* @param Request $request
*
- * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
+ * @return View|RedirectResponse
*/
public function archived(Request $request)
{
@@ -54,8 +59,8 @@ class ContactsController extends Controller
* Display contacts.
*
* @param Request $request
- *
- * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
+ * @param bool $active
+ * @return View|RedirectResponse
*/
private function contacts(Request $request, bool $active)
{
@@ -64,7 +69,11 @@ class ContactsController extends Controller
$showDeceased = $request->input('show_dead');
if ($user->contacts_sort_order !== $sort) {
- $user->updateContactViewPreference($sort);
+ app(UpdateViewPreference::class)->execute([
+ 'account_id' => $user->account->id,
+ 'user_id' => $user->id,
+ 'preference' => $sort,
+ ]);
}
$contacts = $user->account->contacts()->real();
@@ -131,7 +140,7 @@ class ContactsController extends Controller
/**
* Show the form to add a new contact.
*
- * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
+ * @return View|Factory|RedirectResponse
*/
public function create()
{
@@ -141,7 +150,7 @@ class ContactsController extends Controller
/**
* Show the form in case the contact is missing.
*
- * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
+ * @return View|Factory|RedirectResponse
*/
public function missing()
{
@@ -152,7 +161,7 @@ class ContactsController extends Controller
* Show the Add user form unless the contact has limitations.
*
* @param bool $isContactMissing
- * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
+ * @return View|Factory|RedirectResponse
*/
private function createForm($isContactMissing = false)
{
@@ -165,15 +174,15 @@ class ContactsController extends Controller
return view('people.create')
->withIsContactMissing($isContactMissing)
->withGenders(GendersHelper::getGendersInput())
- ->withDefaultGender(auth()->user()->account->default_gender_id);
+ ->withDefaultGender(auth()->user()->account->default_gender_id)
+ ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
* Store the contact.
*
- * @param \Illuminate\Http\Request $request
- *
- * @return \Illuminate\Http\RedirectResponse
+ * @param Request $request
+ * @return RedirectResponse
*/
public function store(Request $request)
{
@@ -208,7 +217,7 @@ class ContactsController extends Controller
*
* @param Contact $contact
*
- * @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse
+ * @return View|RedirectResponse
*/
public function show(Contact $contact)
{
@@ -292,7 +301,7 @@ class ContactsController extends Controller
*
* @param Contact $contact
*
- * @return \Illuminate\View\View
+ * @return View
*/
public function edit(Contact $contact)
{
@@ -318,7 +327,8 @@ class ContactsController extends Controller
->withAge($age)
->withHasBirthdayReminder($hasBirthdayReminder)
->withHasDeceasedReminder($hasDeceasedReminder)
- ->withGenders(GendersHelper::getGendersInput());
+ ->withGenders(GendersHelper::getGendersInput())
+ ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
@@ -327,7 +337,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
*/
public function update(Request $request, Contact $contact)
{
@@ -414,7 +424,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
*/
public function destroy(Request $request, Contact $contact)
{
@@ -439,7 +449,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\View\View
+ * @return View
*/
public function editWork(Request $request, Contact $contact)
{
@@ -453,7 +463,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
*/
public function updateWork(Request $request, Contact $contact)
{
@@ -474,7 +484,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\View\View
+ * @return View
*/
public function editFoodPreferences(Request $request, Contact $contact)
{
@@ -488,7 +498,7 @@ class ContactsController extends Controller
* @param Request $request
* @param Contact $contact
*
- * @return \Illuminate\Http\RedirectResponse
+ * @return RedirectResponse
*/
public function updateFoodPreferences(Request $request, Contact $contact)
{
@@ -625,7 +635,11 @@ class ContactsController extends Controller
$sort = $request->input('sort') ?? $user->contacts_sort_order;
if ($user->contacts_sort_order !== $sort) {
- $user->updateContactViewPreference($sort);
+ app(UpdateViewPreference::class)->execute([
+ 'account_id' => $user->account->id,
+ 'user_id' => $user->id,
+ 'preference' => $sort,
+ ]);
}
$tags = null;
diff --git a/app/Http/Controllers/JournalController.php b/app/Http/Controllers/JournalController.php
index dba4a8e7e..68840f68b 100644
--- a/app/Http/Controllers/JournalController.php
+++ b/app/Http/Controllers/JournalController.php
@@ -6,6 +6,7 @@ use App\Helpers\DateHelper;
use App\Models\Journal\Day;
use Illuminate\Http\Request;
use App\Models\Journal\Entry;
+use App\Helpers\JournalHelper;
use App\Models\Journal\JournalEntry;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
@@ -121,7 +122,7 @@ class JournalController extends Controller
*/
public function hasRated()
{
- if (auth()->user()->hasAlreadyRatedToday()) {
+ if (JournalHelper::hasAlreadyRatedToday(auth()->user())) {
return 'true';
}
diff --git a/app/Http/Middleware/CheckCompliance.php b/app/Http/Middleware/CheckCompliance.php
index 5a0bf0097..546753c2c 100644
--- a/app/Http/Middleware/CheckCompliance.php
+++ b/app/Http/Middleware/CheckCompliance.php
@@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use Closure;
+use App\Helpers\ComplianceHelper;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
@@ -26,7 +27,7 @@ class CheckCompliance
}
if (Auth::check()) {
- if (! auth()->user()->isPolicyCompliant()) {
+ if (! ComplianceHelper::isCompliantWithCurrentTerm(auth()->user())) {
return redirect()->route('compliance');
}
}
diff --git a/app/Http/Resources/Account/User/User.php b/app/Http/Resources/Account/User/User.php
index e8ecb43f1..97999f6a6 100644
--- a/app/Http/Resources/Account/User/User.php
+++ b/app/Http/Resources/Account/User/User.php
@@ -27,7 +27,7 @@ class User extends Resource
'timezone' => $this->timezone,
'currency' => new CurrencyResource($this->currency),
'locale' => $this->locale,
- 'is_policy_compliant' => $this->isPolicyCompliant(),
+ 'is_policy_compliant' => $this->policy_compliant,
'account' => [
'id' => $this->account->id,
],
diff --git a/app/Models/Account/Weather.php b/app/Models/Account/Weather.php
index 89aab6d22..4b8d4e74b 100644
--- a/app/Models/Account/Weather.php
+++ b/app/Models/Account/Weather.php
@@ -130,6 +130,7 @@ class Weather extends Model
* Temperature is fetched in Celsius. It needs to be
* converted to Fahrenheit depending on the user.
*
+ * @param string $scale
* @return string
*/
public function temperature($scale = 'celsius')
diff --git a/app/Models/User/User.php b/app/Models/User/User.php
index 4bffed049..fef110e01 100644
--- a/app/Models/User/User.php
+++ b/app/Models/User/User.php
@@ -3,13 +3,12 @@
namespace App\Models\User;
use Carbon\Carbon;
-use App\Helpers\DateHelper;
use App\Models\Journal\Day;
use App\Models\Settings\Term;
use App\Models\Account\Account;
use App\Models\Contact\Contact;
+use App\Helpers\ComplianceHelper;
use App\Models\Settings\Currency;
-use Illuminate\Support\Facades\DB;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Auth\Notifications\VerifyEmail;
@@ -18,11 +17,8 @@ use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\User as Authenticatable;
-use Illuminate\Database\Eloquent\ModelNotFoundException;
-use App\Http\Resources\Account\User\User as UserResource;
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
-use App\Http\Resources\Settings\Compliance\Compliance as ComplianceResource;
class User extends Authenticatable implements MustVerifyEmail, HasLocalePreference
{
@@ -111,22 +107,33 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
return $this->hasMany(RecoveryCode::class);
}
+ /**
+ * Gets the currency for this user.
+ *
+ * @return BelongsTo
+ */
+ public function currency()
+ {
+ return $this->belongsTo(Currency::class);
+ }
+
/**
* Assigns a default value just in case the sort order is empty.
*
* @param string $value
* @return string
*/
- public function getContactsSortOrderAttribute($value)
+ public function getContactsSortOrderAttribute($value): string
{
return ! empty($value) ? $value : 'firstnameAZ';
}
/**
* Indicates if the layout is fluid or not for the UI.
+ *
* @return string
*/
- public function getFluidLayout()
+ public function getFluidLayout(): string
{
if ($this->fluid_container == 'true') {
return 'container-fluid';
@@ -135,25 +142,13 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
}
}
- /**
- * @return string
- */
- public function getMetricSymbol()
- {
- if ($this->metric == 'fahrenheit') {
- return 'F';
- } else {
- return 'C';
- }
- }
-
/**
* Get users's full name. The name is formatted according to the user's
* preference, either "Firstname Lastname", or "Lastname Firstname".
*
* @return string
*/
- public function getNameAttribute()
+ public function getNameAttribute(): string
{
$completeName = '';
@@ -174,49 +169,10 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
return $completeName;
}
- /**
- * Gets the currency for this user.
- *
- * @return BelongsTo
- */
- public function currency()
- {
- return $this->belongsTo(Currency::class);
- }
-
- /**
- * Set the contact view preference.
- *
- * @param string $preference
- */
- public function updateContactViewPreference($preference)
- {
- $this->contacts_sort_order = $preference;
- $this->save();
- }
-
- /**
- * Indicates whether the user has already rated the current day.
- * @return bool
- */
- public function hasAlreadyRatedToday()
- {
- try {
- Day::where('account_id', $this->account_id)
- ->where('date', now($this->timezone)->toDateString())
- ->firstOrFail();
- } catch (ModelNotFoundException $e) {
- return false;
- }
-
- return true;
- }
-
/**
* Ecrypt the user's google_2fa secret.
*
* @param string $value
- *
* @return void
*/
public function setGoogle2faSecretAttribute($value): void
@@ -237,6 +193,17 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
}
}
+ /**
+ * Indicate if the user has accepted the most current terms and privacy.
+ *
+ * @param string|null $value
+ * @return bool
+ */
+ public function getPolicyCompliantAttribute($value): bool
+ {
+ return ComplianceHelper::isCompliantWithCurrentTerm($this);
+ }
+
/**
* Indicate whether the user should be reminded at this time.
* This is affected by the user settings regarding the hour of the day he
@@ -270,126 +237,12 @@ class User extends Authenticatable implements MustVerifyEmail, HasLocalePreferen
return $isTheRightTime;
}
- /**
- * Indicate if the user has accepted the most current terms and privacy.
- *
- * @return bool
- */
- public function isPolicyCompliant(): bool
- {
- $latestTerm = Term::latest()->first();
-
- if ($this->getStatusForCompliance($latestTerm->id) == false) {
- return false;
- }
-
- return true;
- }
-
- /**
- * Accept latest policy.
- *
- * @return Term|bool
- */
- public function acceptPolicy($ipAddress = null)
- {
- $latestTerm = Term::latest()->first();
-
- if (! $latestTerm) {
- return false;
- }
-
- $this->terms()->syncWithoutDetaching([$latestTerm->id => [
- 'account_id' => $this->account_id,
- 'ip_address' => $ipAddress,
- ]]);
-
- return $latestTerm;
- }
-
- /**
- * Get the status for a given term.
- *
- * @param int $termId
- * @return array|bool
- */
- public function getStatusForCompliance($termId)
- {
- // @TODO: use eloquent to do this instead
- $termUser = DB::table('term_user')->where('user_id', $this->id)
- ->where('account_id', $this->account_id)
- ->where('term_id', $termId)
- ->first();
-
- if (! $termUser) {
- return false;
- }
-
- $compliance = Term::find($termId);
- $signedDate = DateHelper::parseDateTime($termUser->created_at);
-
- return [
- 'signed' => true,
- 'signed_date' => DateHelper::getTimestamp($signedDate),
- 'ip_address' => $termUser->ip_address,
- 'user' => new UserResource($this),
- 'term' => new ComplianceResource($compliance),
- ];
- }
-
- /**
- * Get the list of all the policies the user has signed.
- *
- * @return \Illuminate\Support\Collection
- */
- public function getAllCompliances()
- {
- $terms = collect();
- $termsUser = DB::table('term_user')->where('user_id', $this->id)
- ->get();
-
- foreach ($termsUser as $termUser) {
- $terms->push([
- $this->getStatusForCompliance($termUser->term_id),
- ]);
- }
-
- return $terms;
- }
-
- /**
- * Get the name order that will be used when rendered the Add/Edit forms
- * about contacts.
- *
- * @return string
- */
- public function getNameOrderForForms(): string
- {
- $nameOrder = '';
-
- switch ($this->name_order) {
- case 'firstname_lastname':
- case 'firstname_lastname_nickname':
- case 'firstname_nickname_lastname':
- case 'nickname':
- $nameOrder = 'firstname';
- break;
- case 'lastname_firstname':
- case 'lastname_firstname_nickname':
- case 'lastname_nickname_firstname':
- $nameOrder = 'lastname';
- break;
- }
-
- return $nameOrder;
- }
-
/**
* Send the email verification notification.
*
* @return void
*/
- public function sendEmailVerificationNotification()
+ public function sendEmailVerificationNotification(): void
{
/** @var int $count */
$count = Account::count();
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index 43dbdacda..ae96ca3d0 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -159,5 +159,7 @@ class AppServiceProvider extends ServiceProvider
\App\Services\Account\Settings\ExportAccount::class => \App\Services\Account\Settings\ExportAccount::class,
\App\Services\Account\Settings\ResetAccount::class => \App\Services\Account\Settings\ResetAccount::class,
\App\Services\Account\Settings\DestroyAccount::class => \App\Services\Account\Settings\DestroyAccount::class,
+ \App\Services\User\UpdateViewPreference::class => \App\Services\User\UpdateViewPreference::class,
+ \App\Services\User\AcceptPolicy::class => \App\Services\User\AcceptPolicy::class,
];
}
diff --git a/app/Services/User/AcceptPolicy.php b/app/Services/User/AcceptPolicy.php
new file mode 100644
index 000000000..3dd40169c
--- /dev/null
+++ b/app/Services/User/AcceptPolicy.php
@@ -0,0 +1,57 @@
+ 'required|integer|exists:accounts,id',
+ 'user_id' => 'required|integer|exists:users,id',
+ 'ip_address' => 'nullable|string|max:255',
+ ];
+ }
+
+ /**
+ * Accept the latest user policy.
+ *
+ * @param array $data
+ * @return Term
+ * @throws \Exception
+ */
+ public function execute(array $data): Term
+ {
+ $this->validate($data);
+
+ try {
+ $user = User::where('account_id', $data['account_id'])
+ ->findOrFail($data['user_id']);
+ } catch (ModelNotFoundException $e) {
+ throw new ModelNotFoundException(trans('app.error_user_account'));
+ }
+
+ $latestTerm = Term::latest()->first();
+
+ if (! $latestTerm) {
+ throw new \Exception(trans('app.error_no_term'));
+ }
+
+ $user->terms()->syncWithoutDetaching([$latestTerm->id => [
+ 'account_id' => $user->account_id,
+ 'ip_address' => $this->nullOrValue($data, 'ip_address'),
+ ]]);
+
+ return $latestTerm;
+ }
+}
diff --git a/app/Services/User/CreateUser.php b/app/Services/User/CreateUser.php
index 7e0249109..b9a52ac31 100644
--- a/app/Services/User/CreateUser.php
+++ b/app/Services/User/CreateUser.php
@@ -45,7 +45,11 @@ class CreateUser extends BaseService
$user = $this->setRegionalParameters($user, $ipAddress);
$user->save();
- $user->acceptPolicy($ipAddress);
+ app(AcceptPolicy::class)->execute([
+ 'account_id' => $user->account->id,
+ 'user_id' => $user->id,
+ 'ip_address' => $ipAddress,
+ ]);
return $user;
}
diff --git a/app/Services/User/UpdateViewPreference.php b/app/Services/User/UpdateViewPreference.php
new file mode 100644
index 000000000..2f5225e93
--- /dev/null
+++ b/app/Services/User/UpdateViewPreference.php
@@ -0,0 +1,47 @@
+ 'required|integer|exists:accounts,id',
+ 'user_id' => 'required|integer|exists:users,id',
+ 'preference' => 'required|string|max:255',
+ ];
+ }
+
+ /**
+ * Set the contact view preference.
+ *
+ * @param array $data
+ * @return User
+ */
+ public function execute(array $data): User
+ {
+ $this->validate($data);
+
+ try {
+ $user = User::where('account_id', $data['account_id'])
+ ->findOrFail($data['user_id']);
+ } catch (ModelNotFoundException $e) {
+ throw new ModelNotFoundException(trans('app.error_user_account'));
+ }
+
+ $user->contacts_sort_order = $data['preference'];
+ $user->save();
+
+ return $user;
+ }
+}
diff --git a/database/migrations/2018_05_20_121028_accept_terms.php b/database/migrations/2018_05_20_121028_accept_terms.php
index b9a6e1fc0..229576271 100644
--- a/database/migrations/2018_05_20_121028_accept_terms.php
+++ b/database/migrations/2018_05_20_121028_accept_terms.php
@@ -1,6 +1,7 @@
account) {
- $user->acceptPolicy();
+ app(AcceptPolicy::class)->execute([
+ 'account_id' => $user->account->id,
+ 'user_id' => $user->id,
+ 'ip_address' => null,
+ ]);
}
}
});
diff --git a/public/js/langs/en.json b/public/js/langs/en.json
index 88217bfc6..27d0da5a5 100644
--- a/public/js/langs/en.json
+++ b/public/js/langs/en.json
@@ -1 +1 @@
-{"app":{"add":"Add","another_day":"another day","application_description":"Monica is a tool to manage your interactions with your loved ones, friends, and family.","application_og_title":"Have better relations with your loved ones. Free Online CRM for friends and family.","application_title":"Monica \u2013 personal relationship manager","back":"Back","breadcrumb_add_note":"Add a note","breadcrumb_add_significant_other":"Add significant other","breadcrumb_api":"API","breadcrumb_archived_contacts":"Archived contacts","breadcrumb_dashboard":"Dashboard","breadcrumb_dav":"DAV Resources","breadcrumb_edit_introductions":"How did you meet","breadcrumb_edit_note":"Edit a note","breadcrumb_edit_significant_other":"Edit significant other","breadcrumb_journal":"Journal","breadcrumb_list_contacts":"List of people","breadcrumb_profile":"Profile of :name","breadcrumb_settings":"Settings","breadcrumb_settings_export":"Export","breadcrumb_settings_import":"Import","breadcrumb_settings_import_report":"Import report","breadcrumb_settings_import_upload":"Upload","breadcrumb_settings_personalization":"Personalization","breadcrumb_settings_security":"Security","breadcrumb_settings_security_2fa":"Two Factor Authentication","breadcrumb_settings_subscriptions":"Subscription","breadcrumb_settings_tags":"Tags","breadcrumb_settings_users":"Users","breadcrumb_settings_users_add":"Add a user","cancel":"Cancel","close":"Close","compliance_desc":"We have changed our Terms of Use<\/a> and Privacy Policy<\/a>. By law we have to ask you to review them and accept them so you can continue to use your account.","compliance_desc_end":"We don\u2019t do anything nasty with your data or your account and we never will.","compliance_terms":"Accept new terms and privacy policy","compliance_title":"Sorry for the interruption.","confirm":"Confirm","copy":"Copy","create":"Create","date":"Date","dav_birthdays":"Birthdays","dav_birthdays_description":":name\u2019s contact\u2019s birthdays","dav_contacts":"Contacts","dav_contacts_description":":name\u2019s contacts","dav_tasks":"Tasks","dav_tasks_description":":name\u2019s tasks","default_save_success":"The data has been saved.","delete":"Delete","delete_confirm":"Sure?","done":"Done","download":"Download","edit":"Edit","emotion_adoration":"Adoration","emotion_affection":"Affection","emotion_aggravation":"Aggravation","emotion_agitation":"Agitation","emotion_agony":"Agony","emotion_alarm":"Alarm","emotion_alienation":"Alienation","emotion_amazement":"Amazement","emotion_amusement":"Amusement","emotion_anger":"Anger","emotion_anguish":"Anguish","emotion_annoyance":"Annoyance","emotion_anxiety":"Anxiety","emotion_apprehension":"Apprehension","emotion_arousal":"Arousal","emotion_astonishment":"Astonishment","emotion_attraction":"Attraction","emotion_bitterness":"Bitterness","emotion_bliss":"Bliss","emotion_caring":"Caring","emotion_cheerfulness":"Cheerfulness","emotion_compassion":"Compassion","emotion_contempt":"Contempt","emotion_contentment":"Contentment","emotion_defeat":"Defeat","emotion_dejection":"Dejection","emotion_delight":"Delight","emotion_depression":"Depression","emotion_desire":"Desire","emotion_despair":"Despair","emotion_disappointment":"Disappointment","emotion_disgust":"Disgust","emotion_dislike":"Dislike","emotion_dismay":"Dismay","emotion_displeasure":"Displeasure","emotion_distress":"Distress","emotion_dread":"Dread","emotion_eagerness":"Eagerness","emotion_ecstasy":"Ecstasy","emotion_elation":"Elation","emotion_embarrassment":"Embarrassment","emotion_enjoyment":"Enjoyment","emotion_enthrallment":"Enthrallment","emotion_enthusiasm":"Enthusiasm","emotion_envy":"Envy","emotion_euphoria":"Euphoria","emotion_exasperation":"Exasperation","emotion_excitement":"Excitement","emotion_exhilaration":"Exhilaration","emotion_fear":"Fear","emotion_ferocity":"Ferocity","emotion_fondness":"Fondness","emotion_fright":"Fright","emotion_frustration":"Frustration","emotion_fury":"Fury","emotion_gaiety":"Gaiety","emotion_gladness":"Gladness","emotion_glee":"Glee","emotion_gloom":"Gloom","emotion_glumness":"Glumness","emotion_grief":"Grief","emotion_grouchiness":"Grouchiness","emotion_grumpiness":"Grumpiness","emotion_guilt":"Guilt","emotion_happiness":"Happiness","emotion_hate":"Hate","emotion_homesickness":"Homesickness","emotion_hope":"Hope","emotion_hopelessness":"Hopelessness","emotion_horror":"Horror","emotion_hostility":"Hostility","emotion_humiliation":"Humiliation","emotion_hurt":"Hurt","emotion_hysteria":"Hysteria","emotion_infatuation":"Infatuation","emotion_insecurity":"Insecurity","emotion_insult":"Insult","emotion_irritation":"Irritation","emotion_isolation":"Isolation","emotion_jealousy":"Jealousy","emotion_jolliness":"Jolliness","emotion_joviality":"Joviality","emotion_joy":"Joy","emotion_jubilation":"Jubilation","emotion_liking":"Liking","emotion_loathing":"Loathing","emotion_loneliness":"Loneliness","emotion_longing":"Longing","emotion_love":"Love","emotion_lust":"Lust","emotion_melancholy":"Melancholy","emotion_misery":"Misery","emotion_mortification":"Mortification","emotion_neglect":"Neglect","emotion_nervousness":"Nervousness","emotion_optimism":"Optimism","emotion_outrage":"Outrage","emotion_panic":"Panic","emotion_passion":"Passion","emotion_pity":"Pity","emotion_pleasure":"Pleasure","emotion_pride":"Pride","emotion_primary_anger":"Anger","emotion_primary_fear":"Fear","emotion_primary_joy":"Joy","emotion_primary_love":"Love","emotion_primary_sadness":"Sadness","emotion_primary_surprise":"Surprise","emotion_rage":"Rage","emotion_rapture":"Rapture","emotion_regret":"Regret","emotion_rejection":"Rejection","emotion_relief":"Relief","emotion_remorse":"Remorse","emotion_resentment":"Resentment","emotion_revulsion":"Revulsion","emotion_sadness":"Sadness","emotion_satisfaction":"Satisfaction","emotion_scorn":"Scorn","emotion_secondary_affection":"Affection","emotion_secondary_cheerfulness":"Cheerfulness","emotion_secondary_contentment":"Contentment","emotion_secondary_disappointment":"Disappointment","emotion_secondary_disgust":"Disgust","emotion_secondary_enthrallment":"Enthrallment","emotion_secondary_envy":"Envy","emotion_secondary_exasperation":"Exasperation","emotion_secondary_horror":"Horror","emotion_secondary_irritation":"Irritation","emotion_secondary_longing":"Longing","emotion_secondary_lust":"Lust","emotion_secondary_neglect":"Neglect","emotion_secondary_nervousness":"Nervousness","emotion_secondary_optimism":"Optimism","emotion_secondary_pride":"Pride","emotion_secondary_rage":"Rage","emotion_secondary_relief":"Relief","emotion_secondary_sadness":"Sadness","emotion_secondary_shame":"Shame","emotion_secondary_suffering":"Suffering","emotion_secondary_surprise":"Surprise","emotion_secondary_sympathy":"Sympathy","emotion_secondary_zest":"Zest","emotion_sentimentality":"Sentimentality","emotion_shame":"Shame","emotion_shock":"Shock","emotion_sorrow":"Sorrow","emotion_spite":"Spite","emotion_suffering":"Suffering","emotion_surprise":"Surprise","emotion_sympathy":"Sympathy","emotion_tenderness":"Tenderness","emotion_tenseness":"Tenseness","emotion_terror":"Terror","emotion_thrill":"Thrill","emotion_uneasiness":"Uneasiness","emotion_unhappiness":"Unhappiness","emotion_vengefulness":"Vengefulness","emotion_woe":"Woe","emotion_worry":"Worry","emotion_wrath":"Wrath","emotion_zeal":"Zeal","emotion_zest":"Zest","error_help":"We\u2019ll be right back.","error_id":"Error ID: :id","error_maintenance":"Maintenance in progress. Be right back.","error_save":"We had an error trying to save the data.","error_title":"Whoops! Something went wrong.","error_try_again":"Something went wrong. Please try again.","error_twitter":"Follow our Twitter account<\/a> to be alerted when it\u2019s up again.","error_unauthorized":"You don\u2019t have the right to edit this resource.","error_unavailable":"Service Unavailable","file_selected":"1 file selected...|{count} files selected...","filter":"Filter the list","footer_modal_version_release_away":"You are 1 release behind the latest version available. You should update your instance.|You are :number releases behind the latest version available. You should update your instance.","footer_modal_version_whats_new":"What\u2019s new","footer_new_version":"A new version is available","footer_newsletter":"Newsletter","footer_privacy":"Privacy policy","footer_release":"Release notes","footer_remarks":"Any remarks?","footer_send_email":"Send me an email","footer_source_code":"Contribute","footer_version":"Version: :version","gender_female":"Woman","gender_male":"Man","gender_no_gender":"No gender","gender_none":"Rather not say","go_back":"Go back","header_changelog_link":"Product changes","header_logout_link":"Logout","header_settings_link":"Settings","load_more":"Load more","loading":"Loading...","main_nav_activities":"Activities","main_nav_cta":"Add people","main_nav_dashboard":"Dashboard","main_nav_family":"Contacts","main_nav_journal":"Journal","main_nav_tasks":"Tasks","markdown_description":"Want to format your text nicely? We support Markdown to add bold, italic, lists, and more.","markdown_link":"Read documentation","new":"new","no":"No","percent_uploaded":"{percent}% uploaded","relationship_type_bestfriend":"best friend","relationship_type_bestfriend_female":"best friend","relationship_type_bestfriend_female_with_name":":name\u2019s best friend","relationship_type_bestfriend_with_name":":name\u2019s best friend","relationship_type_boss":"boss","relationship_type_boss_female":"boss","relationship_type_boss_female_with_name":":name\u2019s boss","relationship_type_boss_with_name":":name\u2019s boss","relationship_type_child":"son","relationship_type_child_female":"daughter","relationship_type_child_female_with_name":":name\u2019s daughter","relationship_type_child_with_name":":name\u2019s son","relationship_type_colleague":"colleague","relationship_type_colleague_female":"colleague","relationship_type_colleague_female_with_name":":name\u2019s colleague","relationship_type_colleague_with_name":":name\u2019s colleague","relationship_type_cousin":"cousin","relationship_type_cousin_female":"cousin","relationship_type_cousin_female_with_name":":name\u2019s cousin","relationship_type_cousin_with_name":":name\u2019s cousin","relationship_type_date":"date","relationship_type_date_female":"date","relationship_type_date_female_with_name":":name\u2019s date","relationship_type_date_with_name":":name\u2019s date","relationship_type_ex":"ex-boyfriend","relationship_type_ex_female":"ex-girlfriend","relationship_type_ex_female_with_name":":name\u2019s ex-girlfriend","relationship_type_ex_husband":"ex-husband","relationship_type_ex_husband_female":"ex-wife","relationship_type_ex_husband_female_with_name":":name\u2019s ex-wife","relationship_type_ex_husband_with_name":":name\u2019s ex-husband","relationship_type_ex_with_name":":name\u2019s ex-boyfriend","relationship_type_friend":"friend","relationship_type_friend_female":"friend","relationship_type_friend_female_with_name":":name\u2019s friend","relationship_type_friend_with_name":":name\u2019s friend","relationship_type_godfather":"godfather","relationship_type_godfather_female":"godmother","relationship_type_godfather_female_with_name":":name\u2019s godmother","relationship_type_godfather_with_name":":name\u2019s godfather","relationship_type_godson":"godson","relationship_type_godson_female":"goddaughter","relationship_type_godson_female_with_name":":name\u2019s goddaughter","relationship_type_godson_with_name":":name\u2019s godson","relationship_type_grandchild":"grand child","relationship_type_grandchild_female":"grand child","relationship_type_grandchild_female_with_name":":name\u2019s grand child","relationship_type_grandchild_with_name":":name\u2019s grand child","relationship_type_grandparent":"grand parent","relationship_type_grandparent_female":"grand parent","relationship_type_grandparent_female_with_name":":name\u2019s grand parent","relationship_type_grandparent_with_name":":name\u2019s grand parent","relationship_type_group_family":"Family relationships","relationship_type_group_friend":"Friend relationships","relationship_type_group_love":"Love relationships","relationship_type_group_other":"Other kind of relationships","relationship_type_group_work":"Work relationships","relationship_type_inlovewith":"in love with","relationship_type_inlovewith_female":"in love with","relationship_type_inlovewith_female_with_name":"someone :name is in love with","relationship_type_inlovewith_with_name":"someone :name is in love with","relationship_type_lovedby":"loved by","relationship_type_lovedby_female":"loved by","relationship_type_lovedby_female_with_name":":name\u2019s secret lover","relationship_type_lovedby_with_name":":name\u2019s secret lover","relationship_type_lover":"lover","relationship_type_lover_female":"lover","relationship_type_lover_female_with_name":":name\u2019s lover","relationship_type_lover_with_name":":name\u2019s lover","relationship_type_mentor":"mentor","relationship_type_mentor_female":"mentor","relationship_type_mentor_female_with_name":":name\u2019s mentor","relationship_type_mentor_with_name":":name\u2019s mentor","relationship_type_nephew":"nephew","relationship_type_nephew_female":"niece","relationship_type_nephew_female_with_name":":name\u2019s niece","relationship_type_nephew_with_name":":name\u2019s nephew","relationship_type_parent":"father","relationship_type_parent_female":"mother","relationship_type_parent_female_with_name":":name\u2019s mother","relationship_type_parent_with_name":":name\u2019s father","relationship_type_partner":"significant other","relationship_type_partner_female":"significant other","relationship_type_partner_female_with_name":":name\u2019s significant other","relationship_type_partner_with_name":":name\u2019s significant other","relationship_type_protege":"protege","relationship_type_protege_female":"protege","relationship_type_protege_female_with_name":":name\u2019s protege","relationship_type_protege_with_name":":name\u2019s protege","relationship_type_sibling":"brother","relationship_type_sibling_female":"sister","relationship_type_sibling_female_with_name":":name\u2019s sister","relationship_type_sibling_with_name":":name\u2019s brother","relationship_type_spouse":"spouse","relationship_type_spouse_female":"spouse","relationship_type_spouse_female_with_name":":name\u2019s spouse","relationship_type_spouse_with_name":":name\u2019s spouse","relationship_type_stepchild":"stepson","relationship_type_stepchild_female":"stepdaughter","relationship_type_stepchild_female_with_name":":name\u2019s stepdaughter","relationship_type_stepchild_with_name":":name\u2019s stepson","relationship_type_stepparent":"stepfather","relationship_type_stepparent_female":"stepmother","relationship_type_stepparent_female_with_name":":name\u2019s stepmother","relationship_type_stepparent_with_name":":name\u2019s stepfather","relationship_type_subordinate":"subordinate","relationship_type_subordinate_female":"subordinate","relationship_type_subordinate_female_with_name":":name\u2019s subordinate","relationship_type_subordinate_with_name":":name\u2019s subordinate","relationship_type_uncle":"uncle","relationship_type_uncle_female":"aunt","relationship_type_uncle_female_with_name":":name\u2019s aunt","relationship_type_uncle_with_name":":name\u2019s uncle","remove":"Remove","retry":"Retry","revoke":"Revoke","save":"Save","save_close":"Save and close","today":"today","type":"Type","unknown":"I don\u2019t know","update":"Update","upgrade":"Upgrade to unlock","upload":"Upload","verify":"Verify","weather_clear-day":"Clear day","weather_clear-night":"Clear night","weather_cloudy":"Cloudy","weather_current_temperature_celsius":":temperature \u00b0C","weather_current_temperature_fahrenheit":":temperature \u00b0F","weather_current_title":"Current weather","weather_fog":"Fog","weather_partly-cloudy-day":"Partly cloudy day","weather_partly-cloudy-night":"Partly cloudy night","weather_rain":"Rain","weather_sleet":"Sleet","weather_snow":"Snow","weather_wind":"Wind","with":"with","yes":"Yes","yesterday":"yesterday","zoom":"Zoom"},"auth":{"2fa_one_time_password":"Two factor authentication code","2fa_otp_help":"Open up your two factor authentication mobile app and copy the code","2fa_recuperation_code":"Enter a two factor recovery code","2fa_title":"Two Factor Authentication","2fa_wrong_validation":"The two factor authentication has failed.","back_homepage":"Back to homepage","button_remember":"Remember Me","change_language":"Change language to :lang","change_language_title":"Change language:","confirmation_again":"If you want to change your email address you can click here<\/a>.","confirmation_check":"Before proceeding, please check your email for a verification link.","confirmation_fresh":"A fresh verification link has been sent to your email address.","confirmation_request_another":"If you did not receive the email click here to request another<\/a>.","confirmation_title":"Verify Your Email Address","create_account":"Create the first account by signing up<\/a>","email":"Email","email_change_current_email":"Current email address:","email_change_new":"New email address","email_change_title":"Change your email address","email_changed":"Your email address has been changed. Check your mailbox to validate it.","failed":"These credentials do not match our records.","login":"Login","login_again":"Please login again to your account","login_to_account":"Login to your account","login_with_recovery":"Login with a recovery code","mfa_auth_otp":"Authenticate with your two factor device","mfa_auth_u2f":"Authenticate with a U2F device","mfa_auth_webauthn":"Authenticate with a security key (WebAuthn)","not_authorized":"You are not authorized to execute this action","password":"Password","password_forget":"Forget your password?","password_reset":"Reset your password","password_reset_action":"Reset Password","password_reset_email":"E-Mail Address","password_reset_email_content":"Click here to reset your password:","password_reset_password":"Password","password_reset_password_confirm":"Confirm Password","password_reset_send_link":"Send Password Reset Link","password_reset_title":"Reset Password","recovery":"Recovery code","register_action":"Register","register_create_account":"You need to create an account to use Monica","register_email":"Enter a valid email address","register_email_example":"you@home","register_firstname":"First name","register_firstname_example":"eg. John","register_invitation_email":"For security purposes, please indicate the email of the person who\u2019ve invited you to join this account. This information is provided in the invitation email.","register_lastname":"Last name","register_lastname_example":"eg. Doe","register_login":"Log in<\/a> if you already have an account.","register_password":"Password","register_password_confirmation":"Password confirmation","register_password_example":"Enter a secure password","register_policy":"Signing up signifies you\u2019ve read and agree to our Privacy Policy<\/a> and Terms of use<\/a>.","register_title_create":"Create your Monica account","register_title_welcome":"Welcome to your newly installed Monica instance","signup":"Sign up","signup_disabled":"Registration is currently disabled","signup_no_account":"Don\u2019t have an account?","throttle":"Too many login attempts. Please try again in :seconds seconds.","u2f_otp_extension":"U2F is supported natively on Chrome, Firefox<\/a> and Opera. On old Firefox versions, install the U2F Support Add-on<\/a>.","use_recovery":"Or you can use a recovery code<\/a>"},"changelog":{"note":"Note: unfortunately, this page is only in English.","title":"Product changes"},"dashboard":{"dashboard_blank_cta":"Add your first contact","dashboard_blank_description":"Monica is the place to organize all the interactions you have with the ones you care about.","dashboard_blank_illustration":"Illustration by Freepik<\/a>","dashboard_blank_title":"Welcome to your account!","debts_you_owe":"You owe","notes_title":"You don\u2019t have any starred notes yet.","product_changes":"Product changes","product_view_details":"View details","reminders_next_months":"Events in the next 3 months","reminders_none":"No reminder for this month","statistics_activities":"Activities","statistics_contacts":"Contacts","statistics_gifts":"Gifts","tab_calls_blank":"You haven\u2019t logged a call yet.","tab_debts":"Debts","tab_debts_blank":"You haven\u2019t logged any debt yet.","tab_favorite_notes":"Favorite notes","tab_recent_calls":"Recent calls","tab_tasks":"Tasks","tab_tasks_blank":"You haven\u2019t any task yet.","task_add_cta":"Add a task","tasks_add_note":"Press Enter<\/kbd> to add the task.","tasks_add_task_placeholder":"What is this task about?","tasks_tab_your_contacts":"Tasks related to your contacts","tasks_tab_your_tasks":"Your tasks"},"format":{"full_date_year":"F d, Y","full_hour":"h.i A","full_month":"F","full_month_year":"F Y","short_date":"M d","short_date_year":"M d, Y","short_date_year_time":"M d, Y H:i","short_day":"D","short_month":"M","short_month_year":"M Y","short_text":"{text}\u2026"},"journal":{"delete_confirmation":"Are you sure you want to delete this journal entry?","entry_delete_success":"The journal entry has been successfully deleted.","journal_add":"Add a journal entry","journal_add_comment":"Care to add a comment (optional)?","journal_add_cta":"Save","journal_add_date":"Date","journal_add_post":"Entry","journal_add_title":"Title (optional)","journal_blank_cta":"Add your first journal entry","journal_blank_description":"The journal lets you write events that happened to you, and remember them.","journal_come_back":"Thanks. Come back tomorrow to rate your day again.","journal_created_automatically":"Created automatically","journal_description":"Note: the journal lists both manual journal entries, and automatic entries like Activities done with your contacts. While you can delete journal entries manually, you\u2019ll have to delete the activity directly on the contact page.","journal_edit":"Edit a journal entry","journal_empty":"Empty journal","journal_entry_rate":"You rated your day.","journal_entry_type_activity":"Activity","journal_entry_type_journal":"Journal entry","journal_rate":"How was your day? You can rate it once a day.","journal_show_comment":"Show comment"},"mail":{"comment":"Comment: :comment","confirmation_email_bottom":"If you did not create an account, no further action is required.","confirmation_email_button":"Verify email address","confirmation_email_intro":"To validate your email click on the button below","confirmation_email_title":"Monica \u2013 Email verification","footer_contact_info":"Add, view, complete, and change information about this contact:","footer_contact_info2":"See :name\u2019s profile","footer_contact_info2_link":"See :name\u2019s profile: :url","for":"For: :name","greetings":"Hi :username","invitation_button":"Accept invitation","invitation_expiration":"This link will expire in :count days.","invitation_intro":"You\u2019ve been invited by :name (:email) to use Monica, a nice Personal Relationship Management tool.","invitation_link":"To accept the invitation, click on the link below:","invitation_title":"Monica \u2013 You are invited by :name","notification_description":"In :count days (on :date), the following event will happen:","notification_subject_line":"You have an upcoming event","notifications_footer":"If you\u2019re having trouble clicking the \":actionText\" button, copy and paste the URL below into your web browser: [:actionURL](:actionURL)","notifications_hello":"Hello!","notifications_regards":"Regards","notifications_rights":"All rights reserved","notifications_whoops":"Whoops!","password_reset_bottom":"If you did not request a password reset, no further action is required.","password_reset_button":"Reset Password","password_reset_expiration":"This password reset link will expire in :count minutes.","password_reset_intro":"You are receiving this email because we received a password reset request for your account.","password_reset_title":"Monica \u2013 Reset Password Notification","stay_in_touch_subject_description":"You asked to be reminded to stay in touch with :name every :frequency day.|You asked to be reminded to stay in touch with :name every :frequency days.","stay_in_touch_subject_line":"Stay in touch with :name","subject_line":"Reminder for :contact","want_reminded_of":"You wanted to be reminded of :reason"},"pagination":{"next":"Next \u276f","previous":"\u276e Previous"},"passwords":{"changed":"Password changed successfully.","invalid":"Current password you entered is not correct.","reset":"Your password has been reset!","sent":"If the email you entered exists in our records, you\u2019ve been sent a password reset link.","throttled":"Please wait before retrying.","token":"This password reset token is invalid.","user":"If the email you entered exists in our records, you\u2019ve been sent a password reset link."},"people":{"activities_activity":"Activity Category","activities_add_activity":"Add activity","activities_add_category":"Indicate a category","activities_add_date_occured":"The activity happened on...","activities_add_emotions":"Add emotions","activities_add_emotions_title":"Do you want to log how you felt during this activity? (optional)","activities_add_error":"Error when adding the activity","activities_add_more_details":"Add more details","activities_add_participants":"Who, apart from {name}, participated in this activity? (optional)","activities_add_participants_cta":"Add participants","activities_add_pick_activity":"(Optional) Would you like to categorize this activity? You don\u2019t have to, but it will give you statistics later on","activities_add_success":"The activity has been added successfully","activities_add_title":"What did you do with {name}?","activities_blank_add_activity":"Add an activity","activities_blank_title":"Keep track of what you\u2019ve done with {name} in the past, and what you\u2019ve talked about","activities_delete_success":"The activity has been deleted successfully","activities_item_information":":Activity. Happened on :date","activities_list_category":"Category:","activities_list_date":"Happened on","activities_list_emotions":"Emotions felt:","activities_list_participants":"Participants:","activities_profile_number_occurences":":value activity|:value activities","activities_profile_subtitle":"You\u2019ve logged :total_activities activity with :name in total and :activities_last_twelve_months in the last 12 months so far.|You\u2019ve logged :total_activities activities with :name in total and :activities_last_twelve_months in the last 12 months so far.","activities_profile_title":"Activities report between :name and you","activities_profile_year_summary":"Here is what you two have done in :year","activities_profile_year_summary_activity_types":"Here is a breakdown of the type of activities you\u2019ve done together in :year","activities_summary":"Describe what you did","activities_update_success":"The activity has been updated successfully","activities_view_activities_report":"View activities report","activities_who_was_involved":"Who was involved?","activity_title":"Activities","activity_type_ate_at_his_place":"ate at their place","activity_type_ate_at_home":"ate at home","activity_type_ate_restaurant":"ate at a restaurant","activity_type_category_cultural_activities":"Cultural activities","activity_type_category_food":"Food","activity_type_category_simple_activities":"Simple activities","activity_type_category_sport":"Sport","activity_type_did_sport_activities_together":"did sport together","activity_type_just_hung_out":"just hung out","activity_type_picnicked":"picnicked","activity_type_talked_at_home":"just talked at home","activity_type_watched_movie_at_home":"watched a movie at home","activity_type_went_bar":"went to a bar","activity_type_went_concert":"went to a concert","activity_type_went_museum":"went to the museum","activity_type_went_play":"went to a play","activity_type_went_theater":"went to the theater","age_approximate_in_years":"around :age years old","age_exact_birthdate":"born :date","age_exact_in_years":":age years old","avatar_adorable_avatar":"The Adorable avatar","avatar_change_title":"Change your avatar","avatar_current":"Keep the current avatar","avatar_default_avatar":"The default avatar","avatar_gravatar":"The Gravatar associated with the email address of this person. Gravatar<\/a> is a global system that lets users associate email addresses with photos.","avatar_photo":"From a photo that you upload","avatar_question":"Which avatar would you like to use?","birthdate_not_set":"Birthdate is not set","call_blank_desc":"You called {name}","call_blank_title":"Keep track of the phone calls you\u2019ve done with {name}","call_button":"Log a call","call_delete_confirmation":"Are you sure you want to delete this call?","call_delete_success":"The call has been deleted successfully","call_emotions":"Emotions:","call_empty_comment":"No details","call_he_called":"{name} called","call_title":"Phone calls","call_you_called":"You called","calls_add_success":"The phone call has been saved.","contact_address_form_city":"City (optional)","contact_address_form_country":"Country (optional)","contact_address_form_latitude":"Latitude (numbers only) (optional)","contact_address_form_longitude":"Longitude (numbers only) (optional)","contact_address_form_name":"Label (optional)","contact_address_form_postal_code":"Postal code (optional)","contact_address_form_province":"Province (optional)","contact_address_form_street":"Street (optional)","contact_address_title":"Addresses","contact_archive":"Archive contact","contact_archive_help":"Archived contacts will not be shown on the contact list, but still appear in search results.","contact_info_address":"Lives in","contact_info_form_contact_type":"Contact type","contact_info_form_content":"Content","contact_info_form_personalize":"Personalize","contact_info_title":"Contact information","contact_unarchive":"Unarchive contact","conversation_add_another":"Add another message","conversation_add_content":"Write down what was said","conversation_add_error":"You must add at least one message.","conversation_add_how":"How did you communicate?","conversation_add_success":"The conversation has been successfully added.","conversation_add_title":"Record a new conversation","conversation_add_what_was_said":"What did you say?","conversation_add_when":"When did you have this conversation?","conversation_add_who_wrote":"Who said this message?","conversation_add_you":"You","conversation_blank":"Record conversations you have with :name on social media, SMS, ...","conversation_delete_link":"Delete the conversation","conversation_delete_success":"The conversation has been successfully deleted.","conversation_edit_delete":"Are you sure you want to delete this conversation? Deletion is permanent.","conversation_edit_success":"The conversation has been successfully updated.","conversation_edit_title":"Edit conversation","conversation_list_cta":"Log conversation","conversation_list_table_content":"Partial content (last message)","conversation_list_table_messages":"Messages","conversation_list_title":"Conversations","debt_add_add_cta":"Add debt","debt_add_amount":"the sum of","debt_add_cta":"Add debt","debt_add_reason":"for the following reason (optional)","debt_add_success":"The debt has been added successfully","debt_add_they_owe":":name owes you","debt_add_title":"Debt management","debt_add_you_owe":"You owe :name","debt_delete_confirmation":"Are you sure you want to delete this debt?","debt_delete_success":"The debt has been deleted successfully","debt_edit_success":"The debt has been updated successfully","debt_edit_update_cta":"Update debt","debt_they_owe":":name owes you :amount","debt_title":"Debts","debt_you_owe":"You owe :amount","debts_blank_title":"Manage debts you owe to :name or :name owes you","deceased_add_reminder":"Add a reminder for this date","deceased_age":"Age at death","deceased_date_label":"Deceased date","deceased_know_date":"I know the date this person died","deceased_label":"Deceased","deceased_label_with_date":"Deceased on :date","deceased_mark_person_deceased":"Mark this person as deceased","deceased_reminder_title":"Anniversary of the death of :name","document_list_blank_desc":"Here you can store documents related to this person.","document_list_cta":"Upload document","document_list_title":"Documents","document_upload_zone_cta":"Upload a file","document_upload_zone_error":"There was an error uploading the document. Please try again below.","document_upload_zone_progress":"Uploading the document...","edit_contact_information":"Edit contact information","emotion_this_made_me_feel":"This made you feel\u2026","food_preferences_add_success":"Food preferences have been saved","food_preferences_cta":"Add food preferences","food_preferences_edit_cta":"Save food preferences","food_preferences_edit_description":"Perhaps :firstname or someone in the :family\u2019s family has an allergy. Or doesn\u2019t like a specific bottle of wine. Indicate them here so you will remember it next time you invite them for dinner","food_preferences_edit_description_no_last_name":"Perhaps :firstname has an allergy. Or doesn\u2019t like a specific bottle of wine. Indicate them here so you will remember it next time you invite them for dinner","food_preferences_edit_title":"Indicate food preferences","food_preferences_title":"Food preferences","gifts_add_comment":"Comment (optional)","gifts_add_gift":"Add a gift","gifts_add_gift_already_offered":"Gift offered","gifts_add_gift_idea":"Gift idea","gifts_add_gift_name":"Gift name","gifts_add_gift_received":"Gift received","gifts_add_gift_title":"What is this gift?","gifts_add_link":"Link to the web page (optional)","gifts_add_photo":"Photo (optional)","gifts_add_photo_title":"Add a photo for this gift","gifts_add_recipient":"Recipient (optional)","gifts_add_recipient_field":"Recipient","gifts_add_someone":"This gift is for someone in {name}\u2019s family in particular","gifts_add_success":"The gift has been added successfully","gifts_add_title":"Gift management for :name","gifts_add_value":"Value (optional)","gifts_delete_confirmation":"Are you sure you want to delete this gift?","gifts_delete_cta":"Delete","gifts_delete_success":"The gift has been deleted successfully","gifts_delete_title":"Delete a gift","gifts_for":"For: {name}","gifts_ideas":"Gift ideas","gifts_link":"Link","gifts_mark_offered":"Mark as offered","gifts_offered":"Gifts offered","gifts_offered_as_an_idea":"Mark as an idea","gifts_received":"Gifts received","gifts_title":"Gifts","gifts_update_success":"The gift has been updated successfully","gifts_view_comment":"View comment","information_edit_birthdate_label":"Birthdate","information_edit_description":"Description (Optional)","information_edit_description_help":"Used on the contact list to add some context, if necessary.","information_edit_exact":"I know the exact birthdate of this person...","information_edit_firstname":"First name","information_edit_lastname":"Last name (Optional)","information_edit_max_size":"Max :size Kb.","information_edit_max_size2":"Max {size} Kb.","information_edit_not_year":"I know the day and month of the birthdate of this person, but not the year\u2026","information_edit_probably":"This person is probably...","information_edit_success":"The profile has been updated successfully","information_edit_title":"Edit :name\u2019s personal information","information_edit_unknown":"I do not know this person\u2019s age","information_no_work_defined":"No work information defined","information_work_at":"at :company","introductions_add_reminder":"Add a reminder to celebrate this encounter on the anniversary this event happened","introductions_additional_info":"Explain how and where you met","introductions_blank_cta":"Indicate how you met :name","introductions_edit_met_through":"Has someone introduced you to this person?","introductions_first_met_date":"Date you met","introductions_first_met_date_known":"This is the date we met","introductions_met_date":"Met on :date","introductions_met_through":"Met through :name<\/a>","introductions_no_first_met_date":"I don\u2019t know the date we met","introductions_no_met_through":"No one","introductions_reminder_title":"Anniversary of the day you first met","introductions_sidebar_title":"How you met","introductions_title_edit":"How did you meet :name?","introductions_update_success":"You\u2019ve successfully updated the information about how you met this person","last_activity_date":"Last activity together: :date","last_activity_date_empty":"Last activity together: unknown","last_called":"Last called: :date","last_called_empty":"Last called: unknown","life_event_blank":"Log what happens to the life of {name} for your future reference.","life_event_create_add_yearly_reminder":"Add a yearly reminder for this event","life_event_create_category":"All categories","life_event_create_date":"You do not need to indicate a month or a day - only the year is mandatory.","life_event_create_default_description":"Add information about what you know","life_event_create_default_story":"Story (optional)","life_event_create_default_title":"Title (optional)","life_event_create_life_event":"Add life event","life_event_create_success":"The life event has been added","life_event_date_it_happened":"Date it happened","life_event_delete_description":"Are you sure you want to delete this life event? Deletion is permanent.","life_event_delete_success":"The life event has been deleted","life_event_delete_title":"Delete a life event","life_event_list_cta":"Add life event","life_event_list_tab_life_events":"Life events","life_event_list_tab_other":"Notes, reminders, ...","life_event_list_title":"Life events","life_event_sentence_achievement_or_award":"Got an achievement or award","life_event_sentence_anniversary":"Anniversary","life_event_sentence_bought_a_home":"Bought a home","life_event_sentence_broken_bone":"Broke a bone","life_event_sentence_changed_beliefs":"Changed beliefs","life_event_sentence_dentist":"Went to the dentist","life_event_sentence_end_of_relationship":"Ended a relationship","life_event_sentence_engagement":"Got engaged","life_event_sentence_expecting_a_baby":"Expects a baby","life_event_sentence_first_kiss":"Kissed for the first time","life_event_sentence_first_word":"Spoke for the first time","life_event_sentence_holidays":"Went on holidays","life_event_sentence_home_improvement":"Made a home improvement","life_event_sentence_loss_of_a_loved_one":"Lost a loved one","life_event_sentence_marriage":"Got married","life_event_sentence_military_service":"Started military service","life_event_sentence_moved":"Moved","life_event_sentence_new_child":"Had a child","life_event_sentence_new_eating_habits":"Started new eating habits","life_event_sentence_new_family_member":"Added a family member","life_event_sentence_new_hobby":"Started a hobby","life_event_sentence_new_instrument":"Learned a new instrument","life_event_sentence_new_job":"Started a new job","life_event_sentence_new_language":"Learned a new language","life_event_sentence_new_license":"Got a license","life_event_sentence_new_pet":"Got a pet","life_event_sentence_new_relationship":"Started a relationship","life_event_sentence_new_roommate":"Got a roommate","life_event_sentence_new_school":"Started school","life_event_sentence_new_sport":"Started a sport","life_event_sentence_new_vehicle":"Got a new vehicle","life_event_sentence_overcame_an_illness":"Overcame an illness","life_event_sentence_published_book_or_paper":"Published a paper","life_event_sentence_quit_a_habit":"Quit a habit","life_event_sentence_removed_braces":"Removed braces","life_event_sentence_retirement":"Retired","life_event_sentence_study_abroad":"Studied abroad","life_event_sentence_surgery":"Got a surgery","life_event_sentence_tattoo_or_piercing":"Got a tattoo or piercing","life_event_sentence_travel":"Traveled","life_event_sentence_volunteer_work":"Started volunteering","life_event_sentence_wear_glass_or_contact":"Started to wear glass or contact lenses","life_event_sentence_weight_loss":"Lost weight","list_link_to_active_contacts":"You are viewing archived contacts. See the list of active contacts<\/a> instead.","list_link_to_archived_contacts":"List of archived contacts","me":"This is you","modal_call_comment":"What did you talk about? (optional)","modal_call_emotion":"Do you want to log how you felt during this call? (optional)","modal_call_exact_date":"The phone call happened on","modal_call_title":"Log a call","modal_call_who_called":"Who called?","notes_add_cta":"Add note","notes_create_success":"The note has been created successfully","notes_delete_confirmation":"Are you sure you want to delete this note? Deletion is permanent","notes_delete_success":"The note has been deleted successfully","notes_delete_title":"Delete a note","notes_favorite":"Add\/remove from favorites","notes_update_success":"The note has been saved successfully","people_add_birthday_reminder":"Wish happy birthday to :name","people_add_cta":"Add","people_add_firstname":"First name","people_add_gender":"Gender","people_add_import":"Do you want to import your contacts<\/a>?","people_add_lastname":"Last name (Optional)","people_add_middlename":"Middle name (Optional)","people_add_missing":"No Person Found Add New One Now","people_add_new":"Add new person","people_add_nickname":"Nickname (Optional)","people_add_reminder_for_birthday":"Create an annual reminder for the birthday","people_add_success":":name has been successfully created","people_add_title":"Add a new person","people_delete_confirmation":"Are you sure you want to delete this contact? Deletion is permanent.","people_delete_message":"Delete contact","people_delete_success":"The contact has been deleted","people_edit_email_error":"There is already a contact in your account with this email address. Please choose another one.","people_export":"Export as vCard","people_list_account_upgrade_cta":"Upgrade now","people_list_account_upgrade_title":"Upgrade your account to unlock it to its full potential.","people_list_account_usage":"Your account usage: :current\/:limit contacts","people_list_blank_cta":"Add someone","people_list_blank_title":"You don\u2019t have anyone in your account yet","people_list_clear_filter":"Clear filter","people_list_contacts_per_tags":"1 contact|:count contacts","people_list_filter_tag":"Showing all the contacts tagged with","people_list_filter_untag":"Showing all untagged contacts","people_list_firstnameAZ":"Sort by first name A \u2192 Z","people_list_firstnameZA":"Sort by first name Z \u2192 A","people_list_hide_dead":"Hide deceased people (:count)","people_list_last_updated":"Last consulted:","people_list_lastactivitydateNewtoOld":"Sort by last activity date newest to oldest","people_list_lastactivitydateOldtoNew":"Sort by last activity date oldest to newest","people_list_lastnameAZ":"Sort by last name A \u2192 Z","people_list_lastnameZA":"Sort by last name Z \u2192 A","people_list_number_kids":"1 kid|:count kids","people_list_number_reminders":"1 reminder|:count reminders","people_list_show_dead":"Show deceased people (:count)","people_list_sort":"Sort","people_list_stats":"1 contact|:count contacts","people_list_untagged":"View untagged contacts","people_not_found":"Contact not found","people_save_and_add_another_cta":"Submit and add someone else","people_search":"Search your contacts...","people_search_all":"All","people_search_next":"Next","people_search_no_results":"No results found","people_search_of":"of","people_search_page":"Page","people_search_prev":"Prev","people_search_rows_per_page":"Rows per page:","pets_bird":"Bird","pets_cat":"Cat","pets_create_success":"The pet has been successfully added","pets_delete_success":"The pet has been deleted","pets_dog":"Dog","pets_fish":"Fish","pets_hamster":"Hamster","pets_horse":"Horse","pets_kind":"Kind of pet","pets_name":"Name (optional)","pets_other":"Other","pets_rabbit":"Rabbit","pets_rat":"Rat","pets_reptile":"Reptile","pets_small_animal":"Small animal","pets_title":"Pets","pets_update_success":"The pet has been updated","photo_current_profile_pic":"Current profile picture","photo_delete":"Delete photo","photo_list_blank_desc":"You can store images about this contact. Upload one now!","photo_list_cta":"Upload photo","photo_list_title":"Related photos","photo_make_profile_pic":"Make profile picture","photo_title":"Photos","photo_upload_zone_cta":"Upload a photo","relationship_delete_confirmation":"Are you sure you want to delete this relationship? Deletion is permanent.","relationship_form_add":"Add a new relationship","relationship_form_add_choice":"Who is the relationship with?","relationship_form_add_description":"This will let you treat this person like any other contact.","relationship_form_add_no_existing_contact":"You don\u2019t have any contacts who can be related to :name at the moment.","relationship_form_add_success":"The relationship has been successfully set.","relationship_form_also_create_contact":"Create a Contact entry for this person.","relationship_form_associate_contact":"An existing contact","relationship_form_associate_dropdown":"Search and select an existing contact from the dropdown below","relationship_form_associate_dropdown_placeholder":"Search and select an existing contact","relationship_form_create_contact":"Add a new person","relationship_form_deletion_success":"The relationship has been deleted.","relationship_form_edit":"Edit an existing relationship","relationship_form_is_with":"This person is...","relationship_form_is_with_name":":name is...","relationship_unlink_confirmation":"Are you sure you want to delete this relationship? This person will not be deleted \u2013 only the relationship between the two.","reminder_frequency_day":"every day|every :number days","reminder_frequency_month":"every month|every :number months","reminder_frequency_one_time":"on :date","reminder_frequency_week":"every week|every :number weeks","reminder_frequency_year":"every year|every :number year","reminders_add_cta":"Add reminder","reminders_add_description":"Please remind me to...","reminders_add_error_custom_text":"You need to indicate a text for this reminder","reminders_add_next_time":"When is the next time you would like to be reminded about this?","reminders_add_once":"Remind me about this just once","reminders_add_optional_comment":"Optional comment","reminders_add_recurrent":"Remind me about this every","reminders_add_starting_from":"starting from the date specified above","reminders_add_title":"What would you like to be reminded of about :name?","reminders_birthday":"Birthday of :name","reminders_blank_add_activity":"Add a reminder","reminders_blank_title":"Is there something you want to be reminded of about :name?","reminders_create_success":"The reminder has been added successfully","reminders_cta":"Add a reminder","reminders_delete_confirmation":"Are you sure you want to delete this reminder?","reminders_delete_cta":"Delete","reminders_delete_success":"The reminder has been deleted successfully","reminders_description":"We will send an email for each one of the reminders below. Reminders are sent every morning the day events will happen. Reminders automatically added for birthdates can not be deleted. If you want to change those dates, edit the birthdate of the contacts.","reminders_edit_update_cta":"Update reminder","reminders_free_plan_warning":"You are on the Free plan. No emails are sent on this plan. To receive your reminders by email, upgrade your account.","reminders_next_expected_date":"on","reminders_one_time":"One time","reminders_type_month":"month","reminders_type_week":"week","reminders_type_year":"year","reminders_update_success":"The reminder has been updated successfully","section_contact_information":"Contact information","section_personal_activities":"Activities","section_personal_gifts":"Gifts","section_personal_notes":"Notes","section_personal_reminders":"Reminders","section_personal_tasks":"Tasks","set_favorite":"Favorite contacts are placed at the top of the contact list","stay_in_touch":"Stay in touch","stay_in_touch_frequency":"Stay in touch every day|Stay in touch every {count} days","stay_in_touch_invalid":"The frequency must be a number greater than 0.","stay_in_touch_modal_desc":"We can remind you by email to keep in touch with {firstname} at a regular interval.","stay_in_touch_modal_label":"Send me an email every... {count} day|Send me an email every... {count} days","stay_in_touch_modal_title":"Stay in touch","stay_in_touch_premium":"You need to upgrade your account to make use of this feature","tag_add":"Add tags","tag_add_search":"Add or search tags","tag_edit":"Edit tag","tag_no_tags":"No tags yet","tasks_add_task":"Add a task","tasks_blank_title":"You don\u2019t have any tasks yet.","tasks_complete_success":"The task has changed status successfully","tasks_delete_success":"The task has been deleted successfully","tasks_form_description":"Description (optional)","tasks_form_title":"Title","tasks_title":"Tasks","work_add_cta":"Update work information","work_edit_company":"Company (optional)","work_edit_job":"Job title (optional)","work_edit_success":"Work information have been updated with success","work_edit_title":"Update :name\u2019s job information","work_information":"Work information"},"reminder":{"type_birthday":"Wish happy birthday to","type_birthday_kid":"Wish happy birthday to the kid of","type_email":"Email","type_hangout":"Hangout with","type_lunch":"Lunch with","type_phone_call":"Call"},"settings":{"2fa_disable_description":"Disable Two Factor Authentication for your account. Be careful, your account will not be secured anymore !","2fa_disable_error":"Error when trying to disable Two Factor Authentication","2fa_disable_success":"Two Factor Authentication disabled","2fa_disable_title":"Disable Two Factor Authentication","2fa_enable_description":"Enable Two Factor Authentication to increase security with your account.","2fa_enable_error":"Error when trying to activate Two Factor Authentication","2fa_enable_error_already_set":"Two Factor Authentication is already activated","2fa_enable_otp":"Open up your two factor authentication mobile app and scan the following QR barcode:","2fa_enable_otp_help":"If your two factor authentication mobile app does not support QR barcodes, enter in the following code:","2fa_enable_otp_validate":"Please validate the new device you\u2019ve just set:","2fa_enable_success":"Two Factor Authentication activated","2fa_enable_title":"Enable Two Factor Authentication","2fa_otp_title":"Two Factor Authentication mobile application","2fa_title":"Two Factor Authentication","api_authorized_clients":"List of authorized clients","api_authorized_clients_desc":"This section lists all the clients you\u2019ve authorized to access your application data. You can revoke this authorization at anytime.","api_authorized_clients_name":"Name","api_authorized_clients_none":"There is no authorized client yet.","api_authorized_clients_scopes":"Scopes","api_authorized_clients_title":"Authorized Applications","api_description":"The API can be used to manipulate Monica\u2019s data from an external application, like a mobile application for instance.","api_endpoint":"The API endpoint for this Monica instance is:","api_help":"To use the API, a token is mandatory. You can either create a personal access token (Bearer authentication), or authorize an OAuth client to create it for you. See API documentation<\/a>.","api_oauth_clientid":"Client ID","api_oauth_clients":"Your OAuth clients","api_oauth_clients_desc":"This section lets you register your own OAuth clients.","api_oauth_clients_desc2":"Use this client id to request a new token, and convert authorization codes to access tokens. See Laravel Passport documentation<\/a> for more information.","api_oauth_create":"Create Client","api_oauth_create_new":"Create New Client","api_oauth_edit":"Edit Client","api_oauth_name":"Name","api_oauth_name_help":"Something your users will recognize and trust.","api_oauth_not_created":"You have not created any OAuth clients.","api_oauth_redirecturl":"Redirect URL","api_oauth_redirecturl_help":"Your application\u2019s authorization callback URL.","api_oauth_secret":"Secret","api_oauth_title":"OAuth Clients","api_pao_description":"Make sure you give this token to a source you trust \u2013 as they allow you to access all your data.","api_personal_access_tokens":"Personal access tokens","api_title":"API access","api_token_create":"Create Token","api_token_create_new":"Create New Token","api_token_delete":"Delete","api_token_expire":"Expires at {date}","api_token_help":"Here is your new personal access token. This is the only time it will be shown so don\u2019t lose it! You may now use this token to make API requests.","api_token_name":"Token name","api_token_not_created":"You have not created any personal access tokens.","api_token_scopes":"Scopes","api_token_title":"Personal Access Tokens","archive_cta":"Archive all your contacts","archive_desc":"This will archive all the contacts in your account.","archive_title":"Archive all your contacts in your account","currency":"Currency","dav_caldav_birthdays_export":"Export all birthdays in one file","dav_caldav_tasks_export":"Export all tasks in one file","dav_carddav_export":"Export all contacts in one file","dav_clipboard_copied":"Value copied into your clipboard","dav_connect_help":"You can connect your contacts and\/or calendars with this base url on you phone or computer.","dav_connect_help2":"Use your login (email) and password, or create an API token to authenticate.","dav_copy_help":"Copy into your clipboard","dav_description":"Here you can find all settings to use WebDAV resources for CardDAV and CalDAV exports.","dav_title":"WebDAV","dav_title_caldav":"CalDAV","dav_title_carddav":"CardDAV","dav_url_base":"Base url for all CardDAV and CalDAV resources:","dav_url_caldav_birthdays":"CalDAV url for Birthdays resources:","dav_url_caldav_tasks":"CalDAV url for Tasks resources:","dav_url_carddav":"CardDAV url for Contacts resource:","delete_cta":"Delete account","delete_desc":"Do you wish to delete your account? Warning: deletion is permanent and all your data will be erased permanently. Your subscription (if you have any) will also be immediately cancelled.","delete_notice":"Are you sure to delete your account? There is no turning back.","delete_other_desc":"Just to be clear: your data in the main database will be deleted immediately. However, as described in our privacy policy, we do daily backups of the database in case of failure and this backup is kept for 30 days \u2013 then it\u2019s completely deleted. It\u2019s unrealistic to imagine that we can go in all the backups to delete your specific data. By the way, this data is encrypted on very secure Amazon servers and no one has the encryption key except us. Therefore, your data will completely disappear in 30 days from all the backups, and no one will know this data ever existed in the first place.","delete_title":"Delete your account","email":"Email address","email_help":"This is the email used to login, and this is where you\u2019ll receive your reminders.","email_placeholder":"Enter email","export_be_patient":"Click the button to start the export. It might take several minutes to process the export \u2013 please be patient and do not spam the button.","export_sql_cta":"Export to SQL","export_sql_explanation":"Exporting your data in SQL format allows you to take your data and import it to your own Monica instance. This is only valuable if you do have your own server.","export_sql_link_instructions":"Note: read the instructions<\/a> to learn more about importing this file to your instance.","export_title":"Export your account data","export_title_sql":"Export to SQL","firstname":"First name","import_blank_cta":"Import vCard","import_blank_description":"We can import vCard files that you can get from Google Contacts or your Contact manager.","import_blank_question":"Would you like to import contacts now?","import_blank_title":"You haven\u2019t imported any contacts yet.","import_cta":"Upload contacts","import_in_progress":"The import is in progress. Reload the page in one minute.","import_need_subscription":"Importing data requires a subscription.","import_report_date":"Date of the import","import_report_number_contacts":"Number of contacts in the file","import_report_number_contacts_imported":"Number of imported contacts","import_report_number_contacts_skipped":"Number of skipped contacts","import_report_status_imported":"Imported","import_report_status_skipped":"Skipped","import_report_title":"Importing report","import_report_type":"Type of import","import_result_stat":"Uploaded vCard with 1 contact (:total_imported imported, :total_skipped skipped)|Uploaded vCard with :total_contacts contacts (:total_imported imported, :total_skipped skipped)","import_stat":"You\u2019ve imported :number files so far.","import_title":"Import contacts in your account","import_upload_behaviour":"Import behaviour:","import_upload_behaviour_add":"Add new contacts, skip existing","import_upload_behaviour_help":"Note: Replacing will replace all data found in the vCard, but will keep existing contact fields.","import_upload_behaviour_replace":"Replace existing contacts","import_upload_form_file":"Your .vcf<\/code> or .vCard<\/code> file:","import_upload_rule_cant_revert":"Make sure data is accurate before uploading, as you can\u2019t undo the upload.","import_upload_rule_format":"We support .vcard<\/code> and .vcf<\/code> files.","import_upload_rule_instructions":"Export instructions for Contacts.app (macOS)<\/a> and Google Contacts<\/a>.","import_upload_rule_limit":"Files are limited to 10MB.","import_upload_rule_multiple":"For now, if your contacts have multiple email addresses or phone numbers, only the first entry will be picked up.","import_upload_rule_time":"It might take up to 1 minute to upload the contacts and process them. Be patient.","import_upload_rule_vcard":"We support the vCard 3.0 format, which is the default format for Contacts.app (macOS) and Google Contacts.","import_upload_rules_desc":"We do however have some rules:","import_upload_title":"Import your contacts from a vCard file","import_vcard_contact_exist":"Contact already exists","import_vcard_contact_no_firstname":"No firstname (mandatory)","import_vcard_file_no_entries":"File contains no entries","import_vcard_file_not_found":"File not found","import_vcard_parse_error":"Error when parsing the vCard entry","import_vcard_unknown_entry":"Unknown contact name","import_view_report":"View report","lastname":"Last name","layout":"Layout","layout_big":"Full width of the browser","layout_small":"Maximum 1200 pixels wide","locale":"Language used in the app","locale_ar":"Arabic","locale_cs":"Czech","locale_de":"German","locale_en":"English","locale_en-GB":"English (United Kingdom)","locale_es":"Spanish","locale_fr":"French","locale_he":"Hebrew","locale_help":"Do you want to help translating Monica or add a new language? Please follow this link for more information<\/a>.","locale_hr":"Croatian","locale_it":"Italian","locale_nl":"Dutch","locale_pt":"Portuguese","locale_pt-BR":"Portuguese (Brazil)","locale_ru":"Russian","locale_tr":"Turkish","locale_zh":"Chinese Simplified","name":"Your name: :name","name_order":"Name order","name_order_firstname_lastname":".vcf<\/code> or .vCard<\/code> file:","import_upload_rule_cant_revert":"Make sure data is accurate before uploading, as you can\u2019t undo the upload.","import_upload_rule_format":"We support .vcard<\/code> and .vcf<\/code> files.","import_upload_rule_instructions":"Export instructions for Contacts.app (macOS)<\/a> and Google Contacts<\/a>.","import_upload_rule_limit":"Files are limited to 10MB.","import_upload_rule_multiple":"For now, if your contacts have multiple email addresses or phone numbers, only the first entry will be picked up.","import_upload_rule_time":"It might take up to 1 minute to upload the contacts and process them. Be patient.","import_upload_rule_vcard":"We support the vCard 3.0 format, which is the default format for Contacts.app (macOS) and Google Contacts.","import_upload_rules_desc":"We do however have some rules:","import_upload_title":"Import your contacts from a vCard file","import_vcard_contact_exist":"Contact already exists","import_vcard_contact_no_firstname":"No firstname (mandatory)","import_vcard_file_no_entries":"File contains no entries","import_vcard_file_not_found":"File not found","import_vcard_parse_error":"Error when parsing the vCard entry","import_vcard_unknown_entry":"Unknown contact name","import_view_report":"View report","lastname":"Last name","layout":"Layout","layout_big":"Full width of the browser","layout_small":"Maximum 1200 pixels wide","locale":"Language used in the app","locale_ar":"Arabic","locale_cs":"Czech","locale_de":"German","locale_en":"English","locale_en-GB":"English (United Kingdom)","locale_es":"Spanish","locale_fr":"French","locale_he":"Hebrew","locale_help":"Do you want to help translating Monica or add a new language? Please follow this link for more information<\/a>.","locale_hr":"Croatian","locale_it":"Italian","locale_nl":"Dutch","locale_pt":"Portuguese","locale_pt-BR":"Portuguese (Brazil)","locale_ru":"Russian","locale_tr":"Turkish","locale_zh":"Chinese Simplified","name":"Your name: :name","name_order":"Name order","name_order_firstname_lastname":"
"+d(e.message+"",!0)+"";throw e}}m.options=m.setOptions=function(e){return l(m.defaults,e),p(m.defaults),m},m.getDefaults=f,m.defaults=h,m.Parser=o,m.parser=o.parse,m.Renderer=i,m.TextRenderer=a,m.Lexer=r,m.lexer=r.lex,m.InlineLexer=s,m.inlineLexer=s.output,m.Slugger=c,m.parse=m,e.exports=m},"5lKX":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n("eO9T");t.default=function(e){return(0,r.withParams)({type:"requiredUnless",prop:e},(function(t,n){return!!(0,r.ref)(e,this,n)||(0,r.req)(t)}))}},"5oMp":function(e,t,n){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},"62b2":function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0;var r=n("eO9T");t.default=function(e){return(0,r.withParams)({type:"minValue",min:e},(function(t){return!(0,r.req)(t)||(!/\s/.test(t)||t instanceof Date)&&+t>=+e}))}},"66f7":function(e,t,n){const r=n("SbYC"),o=n("J7Ao"),i=n("hyX7"),a=n("+/fp"),{defaults:s}=n("vbtb"),{merge:c,unescape:l}=n("rUJ1");e.exports=class e{constructor(e){this.tokens=[],this.token=null,this.options=e||s,this.options.renderer=this.options.renderer||new r,this.renderer=this.options.renderer,this.renderer.options=this.options,this.slugger=new o}static parse(t,n){return new e(n).parse(t)}parse(e){this.inline=new i(e.links,this.options),this.inlineText=new i(e.links,c({},this.options,{renderer:new a})),this.tokens=e.reverse();let t="";for(;this.next();)t+=this.tok();return t}next(){return this.token=this.tokens.pop(),this.token}peek(){return this.tokens[this.tokens.length-1]||0}parseText(){let e=this.token.text;for(;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)}tok(){let e="";switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,l(this.inlineText.output(this.token.text)),this.slugger);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":{let t,n,r,o,i="";for(r="",t=0;t