2026-01-15 01:13:44 +00:00
|
|
|
<?php
|
|
|
|
|
|
refactor: settings redesign, Spanish translations, language system, strict_types
Settings pages:
- Redesign layout to match Sendkit (max-w-4xl, space-y-12, Separator sections)
- Merge Members page into Workspace settings with Table, invite Dialog, ConfirmDeleteModal
- Add workspace logo upload/delete routes and controller methods
- Translate all hardcoded strings in Workspace.vue modals
Language system:
- Drop languages table, replace language_id FK with locale string column on users
- Create config/languages.php for available languages and default locale
- Add Spanish (es) translations (13 files)
- Simplify HandleInertiaRequests, ProfileController, RegisteredUserController
Code quality:
- Add declare(strict_types=1) to all PHP files
- Fix MastodonPublisher using wrong attribute (filename -> original_filename)
- Fix HasMediaTest for new has_photo/photo_url accessors
- Fix PublishToSocialPlatformTest type error revealed by strict_types
- Remove orphaned Language model from AppServiceProvider morph map
- Update User TypeScript interface (has_photo, photo_url, locale)
- Eager load media relation on workspaces to prevent N+1
- Add 8 new tests for workspace logo upload/delete
- Update workspace settings test to assert members/invitations props
All 710 tests passing.
2026-03-30 03:20:43 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
namespace Database\Factories;
|
|
|
|
|
|
2026-04-15 01:22:04 +00:00
|
|
|
use App\Models\Account;
|
2026-03-29 22:24:28 +00:00
|
|
|
use App\Models\User;
|
2026-01-15 01:13:44 +00:00
|
|
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
|
|
|
|
use Illuminate\Support\Facades\Hash;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-29 22:24:28 +00:00
|
|
|
* @extends Factory<User>
|
2026-01-15 01:13:44 +00:00
|
|
|
*/
|
|
|
|
|
class UserFactory extends Factory
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* The current password being used by the factory.
|
|
|
|
|
*/
|
|
|
|
|
protected static ?string $password;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Define the model's default state.
|
|
|
|
|
*
|
|
|
|
|
* @return array<string, mixed>
|
|
|
|
|
*/
|
|
|
|
|
public function definition(): array
|
|
|
|
|
{
|
|
|
|
|
return [
|
|
|
|
|
'name' => fake()->name(),
|
|
|
|
|
'email' => fake()->unique()->safeEmail(),
|
|
|
|
|
'email_verified_at' => now(),
|
|
|
|
|
'password' => static::$password ??= Hash::make('password'),
|
2026-05-03 20:42:44 +00:00
|
|
|
'google_id' => null,
|
2026-05-04 21:42:25 +00:00
|
|
|
'github_id' => null,
|
2026-01-15 01:13:44 +00:00
|
|
|
'remember_token' => Str::random(10),
|
2026-04-15 01:22:04 +00:00
|
|
|
'account_id' => Account::factory(),
|
refactor: organize middleware/requests into App/ subdirs, add Resources, fix auth routes
- Move middleware to App/ subdir (HandleInertiaRequests, HandleAppearance,
EnsureSubscribed, EnsureUserSetupIsComplete) matching Sendkit pattern
- Move all Form Requests into organized subdirs (App/Post, App/Workspace,
App/Media, App/Invite, App/Settings, App/Auth)
- Create AuthUserResource and AuthWorkspaceResource for HandleInertiaRequests
shared data (role inside currentWorkspace, matching Sendkit pattern)
- Split auth.php into 3 route groups (no middleware, guest, auth) matching
Sendkit pattern exactly
- Fix UserFactory to include all nullable attributes (current_workspace_id,
stripe_id, pm_type, pm_last_four, trial_ends_at)
- Fix SocialAccountResource (display_name not name)
- Update frontend for new auth prop structure
- 702 tests passing (2 pre-existing Mastodon failures)
2026-03-30 00:13:30 +00:00
|
|
|
'current_workspace_id' => null,
|
2026-01-15 01:13:44 +00:00
|
|
|
'two_factor_secret' => null,
|
|
|
|
|
'two_factor_recovery_codes' => null,
|
|
|
|
|
'two_factor_confirmed_at' => null,
|
2026-05-04 21:42:25 +00:00
|
|
|
'utm_source' => null,
|
|
|
|
|
'utm_medium' => null,
|
|
|
|
|
'utm_campaign' => null,
|
|
|
|
|
'utm_term' => null,
|
|
|
|
|
'utm_content' => null,
|
|
|
|
|
'registration_ip' => null,
|
feat: per-workspace pricing, onboarding, and billing overhaul
Pricing
- Bill per workspace ($12/mo or $120/yr each); Stripe quantity tracks the
workspace count and syncs on workspace create/delete.
- 2,500 AI credits per workspace, pooled at the account level; monthly reset
on the billing anniversary, annual granted upfront (no rollover).
- One social account per network per workspace; remove all count-based limits
(workspace/social/member) and the legacy plan tiers (single Workspace plan).
Onboarding (cloud only: SELF_HOSTED=false + PostHog)
- Replace the /subscribe plan picker with /onboarding persona selection
(Creator/Freelancer/Startup/Agency/Small business/Other), saved on the user
(users.persona) and mirrored to PostHog, then Stripe Checkout on the monthly
price. 8-day trial so Stripe displays 7.
Billing screen
- Remove the Change Plan dialog (dead with a single plan); add an annual-upgrade
banner for monthly subscribers (swapToYearly).
- Current-plan card shows the workspace count instead of the plan name.
System AI
- Brand analyzer / workspace autofill is always allowed and never debits credits
(system feature, not the user's usage).
Self-hosted (SELF_HOSTED=true) bypasses all billing, credit, limit, network,
and onboarding logic.
2026-06-21 23:40:03 +00:00
|
|
|
'persona' => null,
|
2026-01-15 01:13:44 +00:00
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-15 01:22:04 +00:00
|
|
|
public function configure(): static
|
|
|
|
|
{
|
|
|
|
|
return $this->afterCreating(function (User $user) {
|
|
|
|
|
if ($user->account_id && ! $user->account?->owner_id) {
|
|
|
|
|
$user->account->update(['owner_id' => $user->id]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-15 01:13:44 +00:00
|
|
|
/**
|
|
|
|
|
* Indicate that the model's email address should be unverified.
|
|
|
|
|
*/
|
|
|
|
|
public function unverified(): static
|
|
|
|
|
{
|
|
|
|
|
return $this->state(fn (array $attributes) => [
|
|
|
|
|
'email_verified_at' => null,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Indicate that the model has two-factor authentication configured.
|
|
|
|
|
*/
|
|
|
|
|
public function withTwoFactor(): static
|
|
|
|
|
{
|
|
|
|
|
return $this->state(fn (array $attributes) => [
|
|
|
|
|
'two_factor_secret' => encrypt('secret'),
|
|
|
|
|
'two_factor_recovery_codes' => encrypt(json_encode(['recovery-code-1'])),
|
|
|
|
|
'two_factor_confirmed_at' => now(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|