Polish: daily reward via celebration overlay + premium chat to recipient
CI/CD / CI - API (dotnet build + engine sim) (push) Has been cancelled
CI/CD / CI - Web (tsc + next build) (push) Has been cancelled
CI/CD / Deploy - local stack (db + server + web) (push) Has been cancelled

- Daily reward now routes through the global CelebrationOverlay: new "daily"
  variant + coins count-up; claiming closes the daily modal and fires
  celebrate({variant:"daily", coins}). Unifies the "you earned X" moment.
- Premium (pro) gold chat is now visible to the OTHER player: ChatMessage gains
  senderPro; server resolves each participant's plan once (SocialService.IsPro)
  and stamps it on ChatMessageDto; ChatScreen styles incoming bubbles with
  .premium-chat when senderPro. Mock marks ~half its friends pro so it's visible
  offline too.

Verified: tsc + next build + dotnet build all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-06 22:26:28 +03:30
parent 03dfbe1e67
commit 82b2bc0648
8 changed files with 45 additions and 7 deletions
@@ -24,6 +24,7 @@ public class ChatMessageDto
public bool FromMe { get; set; }
public string Text { get; set; } = "";
public long Ts { get; set; }
public bool SenderPro { get; set; }
}
public class ConversationDto
+15 -3
View File
@@ -265,7 +265,9 @@ public class SocialService
.OrderBy(m => m.CreatedAt).ToListAsync();
var unread = msgs.Where(m => m.UserId == peerId && m.PeerId == uid && !m.ReadByPeer).ToList();
if (unread.Count > 0) { unread.ForEach(m => m.ReadByPeer = true); await _db.SaveChangesAsync(); }
return msgs.Select(m => ToDto(m, uid)).ToList();
// Resolve each participant's plan once so premium (pro) senders show gold.
bool uidPro = await IsPro(uid), peerPro = await IsPro(peerId);
return msgs.Select(m => ToDto(m, uid, m.UserId == uid ? uidPro : peerPro)).ToList();
}
public async Task<ChatMessageDto> Send(string uid, string peerId, string text)
@@ -274,14 +276,24 @@ public class SocialService
_db.Messages.Add(m);
await _db.SaveChangesAsync();
await _hub.Clients.User(peerId).SendAsync("chat", new { peerId = uid });
return ToDto(m, uid);
return ToDto(m, uid, await IsPro(uid));
}
private static ChatMessageDto ToDto(MessageRow m, string uid) => new()
/// <summary>True when the user has an active premium (pro) plan.</summary>
private async Task<bool> IsPro(string userId)
{
var row = await _db.Profiles.FindAsync(userId);
if (row == null) return false;
var p = JsonSerializer.Deserialize<ProfileDto>(row.Json, JsonOpts.Default);
return p?.Plan == "pro";
}
private static ChatMessageDto ToDto(MessageRow m, string uid, bool senderPro = false) => new()
{
Id = m.Id.ToString(),
FromMe = m.UserId == uid,
Text = m.Text,
Ts = new DateTimeOffset(m.CreatedAt).ToUnixTimeMilliseconds(),
SenderPro = senderPro,
};
}