diff --git a/app/Http/Controllers/DynamicController.php b/app/Http/Controllers/DynamicController.php
index 998dc5e..16f16af 100644
--- a/app/Http/Controllers/DynamicController.php
+++ b/app/Http/Controllers/DynamicController.php
@@ -54,7 +54,7 @@ class DynamicController extends Controller
$dynamic->load([
'ledgers.media',
- 'participants',
+ 'participants' => fn($query) => $query->withPivot('display_name'),
'chat.messages.user',
'chat.messages.media'
]);
diff --git a/app/Http/Controllers/LedgerController.php b/app/Http/Controllers/LedgerController.php
index 3ec97a4..c50a2c0 100644
--- a/app/Http/Controllers/LedgerController.php
+++ b/app/Http/Controllers/LedgerController.php
@@ -64,7 +64,7 @@ class LedgerController extends Controller
$activityService->updateCursor($request->user(), $ledger);
- $dynamic->load('chat', 'participants');
+ $dynamic->load(['chat', 'participants' => fn($query) => $query->withPivot('display_name')]);
$ledger->load([
'media',
diff --git a/app/Http/Controllers/ParticipantController.php b/app/Http/Controllers/ParticipantController.php
new file mode 100644
index 0000000..969b34c
--- /dev/null
+++ b/app/Http/Controllers/ParticipantController.php
@@ -0,0 +1,24 @@
+validate([
+ 'display_name' => ['required', 'string', 'max:255'],
+ ]);
+
+ $participant = $dynamic->participants()->where('user_id', $request->user()->id)->firstOrFail();
+
+ $dynamic->participants()->updateExistingPivot($participant->id, [
+ 'display_name' => $request->input('display_name'),
+ ]);
+
+ return redirect()->back()->with('success', 'Display name updated successfully!');
+ }
+}
diff --git a/app/Models/Dynamic.php b/app/Models/Dynamic.php
index cebac43..75f41e4 100644
--- a/app/Models/Dynamic.php
+++ b/app/Models/Dynamic.php
@@ -21,7 +21,7 @@ class Dynamic extends Model
public function participants(): BelongsToMany
{
- return $this->belongsToMany(User::class, 'participants')->withPivot('role');
+ return $this->belongsToMany(User::class, 'participants')->withPivot('role', 'display_name');
}
public function ledgers(): HasMany
diff --git a/app/Models/Participant.php b/app/Models/Participant.php
index 09219b1..621a6c7 100644
--- a/app/Models/Participant.php
+++ b/app/Models/Participant.php
@@ -12,5 +12,6 @@ class Participant extends Pivot
'user_id',
'dynamic_id',
'role',
+ 'display_name',
];
}
diff --git a/app/Models/User.php b/app/Models/User.php
index b507c99..afcd538 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -49,6 +49,13 @@ class User extends Authenticatable implements PasskeyUser
return $this->hasMany(ReadCursor::class);
}
+ public function displayNameFor(Dynamic $dynamic): string
+ {
+ $participant = $dynamic->participants()->where('user_id', $this->id)->first();
+
+ return $participant?->pivot?->display_name ?? $this->name;
+ }
+
/**
* Get the attributes that should be cast.
*
diff --git a/database/migrations/2026_06_17_100000_add_display_name_to_participants_table.php b/database/migrations/2026_06_17_100000_add_display_name_to_participants_table.php
new file mode 100644
index 0000000..9f799b2
--- /dev/null
+++ b/database/migrations/2026_06_17_100000_add_display_name_to_participants_table.php
@@ -0,0 +1,28 @@
+string('display_name')->nullable()->after('role');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('participants', function (Blueprint $table) {
+ $table->dropColumn('display_name');
+ });
+ }
+};
diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php
index 2683bea..424d4f0 100644
--- a/database/seeders/DatabaseSeeder.php
+++ b/database/seeders/DatabaseSeeder.php
@@ -56,9 +56,9 @@ class DatabaseSeeder extends Seeder
]);
// Add participants (Test User is owner, Alice is owner, Bob is submissive/participant)
- $velvetSanctuary->participants()->attach($testUser->id, ['role' => 'owner']);
+ $velvetSanctuary->participants()->attach($testUser->id, ['role' => 'owner', 'display_name' => 'The Master']);
$velvetSanctuary->participants()->attach($alice->id, ['role' => 'owner']);
- $velvetSanctuary->participants()->attach($bob->id, ['role' => 'participant']);
+ $velvetSanctuary->participants()->attach($bob->id, ['role' => 'participant', 'display_name' => 'Bitch Boi']);
// Chat has been auto-created by the booted hook on Dynamic
$velvetChat = $velvetSanctuary->chat;
diff --git a/resources/css/components.css b/resources/css/components.css
index 5e63a2c..7460f91 100644
--- a/resources/css/components.css
+++ b/resources/css/components.css
@@ -13,5 +13,6 @@
@import './components/password-input.css';
@import './components/auth-layout.css';
@import './components/chat.css';
-@import './components/lightbox.css';
-@import './components/invite-form.css';
+@import "./components/lightbox.css";
+@import "./components/invite-form.css";
+/*@import "./components/display-name-form.css";*/
diff --git a/resources/js/components/DisplayNameForm.vue b/resources/js/components/DisplayNameForm.vue
new file mode 100644
index 0000000..326fd94
--- /dev/null
+++ b/resources/js/components/DisplayNameForm.vue
@@ -0,0 +1,113 @@
+
+
+
+
+ Display Name for {{ participant.name }}
+
+
+