Звонки
This commit is contained in:
@@ -11,6 +11,8 @@ using Knot.Modules.Conversations.Application.Messages.React;
|
|||||||
using Knot.Modules.Conversations.Domain;
|
using Knot.Modules.Conversations.Domain;
|
||||||
using Knot.Shared.Kernel;
|
using Knot.Shared.Kernel;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using Knot.Contracts.Auth.Domain;
|
||||||
|
using Knot.Contracts.Auth.Application.Abstractions;
|
||||||
|
|
||||||
namespace Knot.Modules.Conversations.Infrastructure.SignalR;
|
namespace Knot.Modules.Conversations.Infrastructure.SignalR;
|
||||||
|
|
||||||
@@ -31,14 +33,16 @@ public sealed class ChatHub : Hub
|
|||||||
private readonly ISender _sender;
|
private readonly ISender _sender;
|
||||||
private readonly IUserContext _userContext;
|
private readonly IUserContext _userContext;
|
||||||
private readonly IChatRepository _chatRepository;
|
private readonly IChatRepository _chatRepository;
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ILogger<ChatHub> _logger;
|
private readonly ILogger<ChatHub> _logger;
|
||||||
private readonly IMemoryCache _cache;
|
private readonly IMemoryCache _cache;
|
||||||
|
|
||||||
public ChatHub(ISender sender, IUserContext userContext, IChatRepository chatRepository, ILogger<ChatHub> logger, IMemoryCache cache)
|
public ChatHub(ISender sender, IUserContext userContext, IChatRepository chatRepository, IUserRepository userRepository, ILogger<ChatHub> logger, IMemoryCache cache)
|
||||||
{
|
{
|
||||||
_sender = sender;
|
_sender = sender;
|
||||||
_userContext = userContext;
|
_userContext = userContext;
|
||||||
_chatRepository = chatRepository;
|
_chatRepository = chatRepository;
|
||||||
|
_userRepository = userRepository;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_cache = cache;
|
_cache = cache;
|
||||||
}
|
}
|
||||||
@@ -284,10 +288,12 @@ public sealed class ChatHub : Hub
|
|||||||
[HubMethodName("call_offer")]
|
[HubMethodName("call_offer")]
|
||||||
public async Task CallOffer(CallOfferRequest request)
|
public async Task CallOffer(CallOfferRequest request)
|
||||||
{
|
{
|
||||||
// Try to get caller info from current user's claims
|
// Fetch fresh user info from repository instead of relying on potentially stale JWT claims
|
||||||
var displayName = Context.User?.FindFirstValue("name") ?? Context.User?.FindFirstValue(ClaimTypes.Name) ?? "User";
|
var user = await _userRepository.GetByIdAsync(_userContext.UserId);
|
||||||
var avatar = Context.User?.FindFirstValue("avatar");
|
|
||||||
var username = Context.User?.FindFirstValue("unique_name") ?? Context.User?.Identity?.Name;
|
var displayName = user?.DisplayName ?? Context.User?.FindFirstValue("name") ?? Context.User?.FindFirstValue(ClaimTypes.Name) ?? "User";
|
||||||
|
var avatar = user?.Avatar ?? Context.User?.FindFirstValue("avatar");
|
||||||
|
var username = user?.Username ?? Context.User?.FindFirstValue("unique_name") ?? Context.User?.Identity?.Name;
|
||||||
|
|
||||||
await SendToUserAsync(request.TargetUserId, "call_incoming", new
|
await SendToUserAsync(request.TargetUserId, "call_incoming", new
|
||||||
{
|
{
|
||||||
@@ -297,7 +303,6 @@ public sealed class ChatHub : Hub
|
|||||||
chatId = request.ChatId,
|
chatId = request.ChatId,
|
||||||
callerInfo = new
|
callerInfo = new
|
||||||
{
|
{
|
||||||
|
|
||||||
id = _userContext.UserId.ToString(),
|
id = _userContext.UserId.ToString(),
|
||||||
displayName = displayName,
|
displayName = displayName,
|
||||||
avatar = avatar,
|
avatar = avatar,
|
||||||
@@ -396,10 +401,12 @@ public sealed class ChatHub : Hub
|
|||||||
var chatId = request.ChatId;
|
var chatId = request.ChatId;
|
||||||
var userId = _userContext.UserId.ToString();
|
var userId = _userContext.UserId.ToString();
|
||||||
|
|
||||||
|
// Fetch fresh user info from repository
|
||||||
|
var user = await _userRepository.GetByIdAsync(_userContext.UserId);
|
||||||
|
|
||||||
var displayName = Context.User?.FindFirstValue("name") ?? Context.User?.FindFirstValue(ClaimTypes.Name) ?? "User";
|
var displayName = user?.DisplayName ?? Context.User?.FindFirstValue("name") ?? Context.User?.FindFirstValue(ClaimTypes.Name) ?? "User";
|
||||||
var avatar = Context.User?.FindFirstValue("avatar");
|
var avatar = user?.Avatar ?? Context.User?.FindFirstValue("avatar");
|
||||||
var username = Context.User?.FindFirstValue("unique_name") ?? Context.User?.Identity?.Name ?? "user";
|
var username = user?.Username ?? Context.User?.FindFirstValue("unique_name") ?? Context.User?.Identity?.Name ?? "user";
|
||||||
|
|
||||||
var userInfo = new ParticipantInfo(userId, username, displayName, avatar);
|
var userInfo = new ParticipantInfo(userId, username, displayName, avatar);
|
||||||
|
|
||||||
|
|||||||
+270
-246
@@ -1,246 +1,270 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
--primary: #9acbff;
|
--primary: #9acbff;
|
||||||
--primary-container: #3096e5;
|
--primary-container: #3096e5;
|
||||||
--on-primary: #003355;
|
--on-primary: #003355;
|
||||||
--on-primary-container: #e0f2ff;
|
--on-primary-container: #e0f2ff;
|
||||||
|
|
||||||
--secondary: #c7c6ca;
|
--secondary: #c7c6ca;
|
||||||
--secondary-container: #48494c;
|
--secondary-container: #48494c;
|
||||||
--on-secondary: #2f3033;
|
--on-secondary: #2f3033;
|
||||||
--on-secondary-container: #e3e2e6;
|
--on-secondary-container: #e3e2e6;
|
||||||
|
|
||||||
--tertiary: #53e16f;
|
--tertiary: #53e16f;
|
||||||
--tertiary-container: #006e25;
|
--tertiary-container: #006e25;
|
||||||
--on-tertiary: #00390a;
|
--on-tertiary: #00390a;
|
||||||
--on-tertiary-container: #affca0;
|
--on-tertiary-container: #affca0;
|
||||||
|
|
||||||
--error: #ffb4ab;
|
--error: #ffb4ab;
|
||||||
--error-container: #93000a;
|
--error-container: #93000a;
|
||||||
--on-error: #690005;
|
--on-error: #690005;
|
||||||
--on-error-container: #ffdad6;
|
--on-error-container: #ffdad6;
|
||||||
|
|
||||||
--surface: #131313;
|
--surface: #131313;
|
||||||
--surface-container-lowest: #0b0b0b;
|
--surface-container-lowest: #0b0b0b;
|
||||||
--surface-container-low: #1b1b1b;
|
--surface-container-low: #1b1b1b;
|
||||||
--surface-container: #201f1f;
|
--surface-container: #201f1f;
|
||||||
--surface-container-high: #2a2a2a;
|
--surface-container-high: #2a2a2a;
|
||||||
--surface-container-highest: #353535;
|
--surface-container-highest: #353535;
|
||||||
|
|
||||||
--on-surface: #f5f5fa;
|
--on-surface: #f5f5fa;
|
||||||
--on-surface-variant: #b4b4c3;
|
--on-surface-variant: #b4b4c3;
|
||||||
--outline: #3c3c4b;
|
--outline: #3c3c4b;
|
||||||
--outline-variant: #2d2d37;
|
--outline-variant: #2d2d37;
|
||||||
|
|
||||||
--ease-ice: cubic-bezier(0.4, 0, 0.2, 1);
|
--ease-ice: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
--shadow-ambient: 0 40px 80px -20px rgba(0, 0, 0, 0.5);
|
--shadow-ambient: 0 40px 80px -20px rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||||
--font-headline: 'Inter', sans-serif;
|
--font-headline: 'Inter', sans-serif;
|
||||||
--font-body: 'Inter', sans-serif;
|
--font-body: 'Inter', sans-serif;
|
||||||
|
|
||||||
--color-primary: var(--primary);
|
--color-primary: var(--primary);
|
||||||
--color-primary-container: var(--primary-container);
|
--color-primary-container: var(--primary-container);
|
||||||
--color-on-primary: var(--on-primary);
|
--color-on-primary: var(--on-primary);
|
||||||
--color-on-primary-container: var(--on-primary-container);
|
--color-on-primary-container: var(--on-primary-container);
|
||||||
|
|
||||||
--color-secondary: var(--secondary);
|
--color-secondary: var(--secondary);
|
||||||
--color-secondary-container: var(--secondary-container);
|
--color-secondary-container: var(--secondary-container);
|
||||||
--color-on-secondary: var(--on-secondary);
|
--color-on-secondary: var(--on-secondary);
|
||||||
--color-on-secondary-container: var(--on-secondary-container);
|
--color-on-secondary-container: var(--on-secondary-container);
|
||||||
|
|
||||||
--color-tertiary: var(--tertiary);
|
--color-tertiary: var(--tertiary);
|
||||||
--color-tertiary-container: var(--tertiary-container);
|
--color-tertiary-container: var(--tertiary-container);
|
||||||
--color-on-tertiary: var(--on-tertiary);
|
--color-on-tertiary: var(--on-tertiary);
|
||||||
--color-on-tertiary-container: var(--on-tertiary-container);
|
--color-on-tertiary-container: var(--on-tertiary-container);
|
||||||
|
|
||||||
--color-error: var(--error);
|
--color-error: var(--error);
|
||||||
--color-error-container: var(--error-container);
|
--color-error-container: var(--error-container);
|
||||||
--color-on-error: var(--on-error);
|
--color-on-error: var(--on-error);
|
||||||
--color-on-error-container: var(--on-error-container);
|
--color-on-error-container: var(--on-error-container);
|
||||||
|
|
||||||
--color-surface: var(--surface);
|
--color-surface: var(--surface);
|
||||||
--color-surface-container-lowest: var(--surface-container-lowest);
|
--color-surface-container-lowest: var(--surface-container-lowest);
|
||||||
--color-surface-container-low: var(--surface-container-low);
|
--color-surface-container-low: var(--surface-container-low);
|
||||||
--color-surface-container: var(--surface-container);
|
--color-surface-container: var(--surface-container);
|
||||||
--color-surface-container-high: var(--surface-container-high);
|
--color-surface-container-high: var(--surface-container-high);
|
||||||
--color-surface-container-highest: var(--surface-container-highest);
|
--color-surface-container-highest: var(--surface-container-highest);
|
||||||
|
|
||||||
--color-on-surface: var(--on-surface);
|
--color-on-surface: var(--on-surface);
|
||||||
--color-on-surface-variant: var(--on-surface-variant);
|
--color-on-surface-variant: var(--on-surface-variant);
|
||||||
--color-outline: var(--outline);
|
--color-outline: var(--outline);
|
||||||
--color-outline-variant: var(--outline-variant);
|
--color-outline-variant: var(--outline-variant);
|
||||||
|
|
||||||
--animate-kinetic-in: slideInUp 0.6s var(--ease-ice) forwards;
|
--animate-kinetic-in: slideInUp 0.6s var(--ease-ice) forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global resets and base styles */
|
/* Global resets and base styles */
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background-color: #131313 !important;
|
background-color: #131313 !important;
|
||||||
/* Force to Obsidian surface */
|
/* Force to Obsidian surface */
|
||||||
color: #f5f5fa !important;
|
color: #f5f5fa !important;
|
||||||
/* Force to high-contrast white */
|
/* Force to high-contrast white */
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base style for all elements if they inherit color incorrectly */
|
/* Base style for all elements if they inherit color incorrectly */
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kinetic Core Components */
|
/* Kinetic Core Components */
|
||||||
.knot-card {
|
.knot-card {
|
||||||
background-color: var(--surface-container-low) !important;
|
background-color: var(--surface-container-low) !important;
|
||||||
border-radius: 2rem;
|
border-radius: 2rem;
|
||||||
/* 32px */
|
/* 32px */
|
||||||
transition: all 0.4s var(--ease-ice);
|
transition: all 0.4s var(--ease-ice);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-card-active {
|
.knot-card-active {
|
||||||
background-color: var(--surface-container) !important;
|
background-color: var(--surface-container) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-input-group {
|
.knot-input-group {
|
||||||
background-color: rgba(53, 53, 53, 0.4) !important;
|
background-color: rgba(53, 53, 53, 0.4) !important;
|
||||||
border-radius: 1.75rem;
|
border-radius: 1.75rem;
|
||||||
padding: 1.25rem 1.6rem;
|
padding: 1.25rem 1.6rem;
|
||||||
border: 1px solid rgba(154, 203, 255, 0.1);
|
border: 1px solid rgba(154, 203, 255, 0.1);
|
||||||
transition: all 0.4s var(--ease-ice);
|
transition: all 0.4s var(--ease-ice);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-input-group:focus-within {
|
.knot-input-group:focus-within {
|
||||||
background-color: rgba(53, 53, 53, 0.6) !important;
|
background-color: rgba(53, 53, 53, 0.6) !important;
|
||||||
border-color: #9acbff !important;
|
border-color: #9acbff !important;
|
||||||
box-shadow: 0 0 0 1px #9acbff, 0 0 40px rgba(154, 203, 255, 0.1);
|
box-shadow: 0 0 0 1px #9acbff, 0 0 40px rgba(154, 203, 255, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-input-group input {
|
.knot-input-group input {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
color: #f5f5fa !important;
|
color: #f5f5fa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-button-primary {
|
.knot-button-primary {
|
||||||
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
|
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
|
||||||
color: #e0f2ff !important;
|
color: #e0f2ff !important;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
font-weight: 900;
|
font-weight: 900;
|
||||||
letter-spacing: 0.05em;
|
letter-spacing: 0.05em;
|
||||||
transition: all 0.4s var(--ease-ice);
|
transition: all 0.4s var(--ease-ice);
|
||||||
}
|
}
|
||||||
|
|
||||||
.knot-button-primary:hover {
|
.knot-button-primary:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
filter: brightness(1.1);
|
filter: brightness(1.1);
|
||||||
box-shadow: 0 24px 32px -12px rgba(48, 150, 229, 0.4);
|
box-shadow: 0 24px 32px -12px rgba(48, 150, 229, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
.glass-effect {
|
.glass-effect {
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
background: rgba(19, 19, 19, 0.6);
|
background: rgba(19, 19, 19, 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
.slide-on-ice {
|
.slide-on-ice {
|
||||||
transition: all 0.4s var(--ease-ice);
|
transition: all 0.4s var(--ease-ice);
|
||||||
}
|
}
|
||||||
|
|
||||||
.animate-kinetic {
|
.animate-kinetic {
|
||||||
animation: var(--animate-kinetic-in);
|
animation: var(--animate-kinetic-in);
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar {
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
width: 4px;
|
width: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-scrollbar::-webkit-scrollbar-thumb {
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
background: var(--outline-variant);
|
background: var(--outline-variant);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overrides for legacy code */
|
/* Overrides for legacy code */
|
||||||
.bg-surface {
|
.bg-surface {
|
||||||
background-color: var(--surface) !important;
|
background-color: var(--surface) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-surface-container-low {
|
.bg-surface-container-low {
|
||||||
background-color: var(--surface-container-low) !important;
|
background-color: var(--surface-container-low) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-surface-container-lowest {
|
.bg-surface-container-lowest {
|
||||||
background-color: var(--surface-container-lowest) !important;
|
background-color: var(--surface-container-lowest) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-surface-container-high {
|
.bg-surface-container-high {
|
||||||
background-color: var(--surface-container-high) !important;
|
background-color: var(--surface-container-high) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-on-surface {
|
.text-on-surface {
|
||||||
color: var(--on-surface) !important;
|
color: var(--on-surface) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-on-surface-variant {
|
.text-on-surface-variant {
|
||||||
color: var(--on-surface-variant) !important;
|
color: var(--on-surface-variant) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-primary {
|
.text-primary {
|
||||||
color: var(--primary) !important;
|
color: var(--primary) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-accent {
|
.text-accent {
|
||||||
color: #9acbff !important;
|
color: #9acbff !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bubble overrides */
|
/* Bubble overrides */
|
||||||
.bubble-sent {
|
.bubble-sent {
|
||||||
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
|
background: linear-gradient(135deg, #9acbff, #3096e5) !important;
|
||||||
color: #e0f2ff !important;
|
color: #e0f2ff !important;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
border-bottom-right-radius: 0.25rem;
|
border-bottom-right-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bubble-received {
|
.bubble-received {
|
||||||
background: var(--surface-container-highest) !important;
|
background: var(--surface-container-highest) !important;
|
||||||
color: var(--on-surface) !important;
|
color: var(--on-surface) !important;
|
||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
border-bottom-left-radius: 0.25rem;
|
border-bottom-left-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Autofill fix - prevent browser blue background on dark theme */
|
/* Autofill fix - prevent browser blue background on dark theme */
|
||||||
input:-webkit-autofill,
|
input:-webkit-autofill,
|
||||||
input:-webkit-autofill:hover,
|
input:-webkit-autofill:hover,
|
||||||
input:-webkit-autofill:focus,
|
input:-webkit-autofill:focus,
|
||||||
input:-webkit-autofill:active {
|
input:-webkit-autofill:active {
|
||||||
-webkit-box-shadow: 0 0 0 1000px #0b0b0b inset !important;
|
-webkit-box-shadow: 0 0 0 1000px #0b0b0b inset !important;
|
||||||
-webkit-text-fill-color: #f5f5fa !important;
|
-webkit-text-fill-color: #f5f5fa !important;
|
||||||
transition: background-color 5000s ease-in-out 0s;
|
transition: background-color 5000s ease-in-out 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes highlightFlash {
|
@keyframes highlightFlash {
|
||||||
0% { background-color: rgba(0, 163, 255, 0.3); }
|
0% { background-color: rgba(0, 163, 255, 0.3); }
|
||||||
100% { background-color: transparent; }
|
100% { background-color: transparent; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.highlight-message {
|
@keyframes callWave {
|
||||||
animation: highlightFlash 2s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
|
0% { transform: scale(1); opacity: 0.5; }
|
||||||
border-radius: 12px;
|
100% { transform: scale(1.5); opacity: 0; }
|
||||||
position: relative;
|
}
|
||||||
z-index: 10;
|
|
||||||
}␍
|
@keyframes wiggle {
|
||||||
|
0%, 100% { transform: rotate(0); }
|
||||||
|
25% { transform: rotate(-10deg); }
|
||||||
|
75% { transform: rotate(10deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-call-wave {
|
||||||
|
animation: callWave 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-call-wave-delayed {
|
||||||
|
animation: callWave 2s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||||
|
animation-delay: 1s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-wiggle {
|
||||||
|
animation: wiggle 0.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-message {
|
||||||
|
animation: highlightFlash 2s cubic-bezier(0.4, 0, 0.2, 1) forwards !important;
|
||||||
|
border-radius: 12px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
@@ -8,20 +8,16 @@ import { useAuthStore } from '../../../auth/application/authStore';
|
|||||||
import { useLang } from '../../../../core/infrastructure/i18n';
|
import { useLang } from '../../../../core/infrastructure/i18n';
|
||||||
import { playCallRingtone, stopCallRingtone, playUnavailableSound } from '../../../../core/utils/sounds';
|
import { playCallRingtone, stopCallRingtone, playUnavailableSound } from '../../../../core/utils/sounds';
|
||||||
import { getMediaUrl } from '../../../../core/utils/utils';
|
import { getMediaUrl } from '../../../../core/utils/utils';
|
||||||
|
import { UserBasic, CallInfo } from '../../../../core/domain/types';
|
||||||
|
|
||||||
type CallState = 'idle' | 'calling' | 'incoming' | 'connected' | 'ended';
|
type CallState = 'idle' | 'calling' | 'incoming' | 'connected' | 'ended';
|
||||||
|
|
||||||
interface CallModalProps {
|
interface CallModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
targetUser: { id: string; displayName?: string; username?: string; avatar?: string | null } | null;
|
targetUser: UserBasic | null;
|
||||||
callType: 'voice' | 'video';
|
callType: 'voice' | 'video';
|
||||||
incoming?: {
|
incoming?: CallInfo | null;
|
||||||
from: string;
|
|
||||||
offer: RTCSessionDescriptionInit;
|
|
||||||
callType: 'voice' | 'video';
|
|
||||||
callerInfo?: { displayName?: string; username?: string; avatar?: string | null } | null;
|
|
||||||
} | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ICE servers cache (fetched from server)
|
// ICE servers cache (fetched from server)
|
||||||
@@ -1614,12 +1610,12 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
};
|
};
|
||||||
|
|
||||||
const displayName = incoming
|
const displayName = incoming
|
||||||
? incoming.callerInfo?.displayName || incoming.callerInfo?.username || '...'
|
? incoming.callerInfo?.displayName || incoming.callerInfo?.userName || incoming.callerInfo?.username || '...'
|
||||||
: targetUser?.displayName || targetUser?.username || '...';
|
: targetUser?.displayName || targetUser?.userName || targetUser?.username || '...';
|
||||||
|
|
||||||
const displayAvatar = incoming
|
const displayAvatar = incoming
|
||||||
? incoming.callerInfo?.avatar
|
? incoming.callerInfo?.avatarUrl || incoming.callerInfo?.avatar
|
||||||
: targetUser?.avatar;
|
: targetUser?.avatarUrl || targetUser?.avatar;
|
||||||
|
|
||||||
const initials = displayName
|
const initials = displayName
|
||||||
.split(' ')
|
.split(' ')
|
||||||
@@ -1655,16 +1651,15 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
className="fixed bottom-6 right-6 z-[100] flex items-center gap-3 px-4 py-3 rounded-2xl glass-strong shadow-2xl shadow-black/50 border border-white/10 cursor-pointer select-none"
|
className="fixed bottom-6 right-6 z-[100] flex items-center gap-3 px-4 py-3 rounded-2xl glass-strong shadow-2xl shadow-black/50 border border-white/10 cursor-pointer select-none"
|
||||||
onClick={() => setIsMinimized(false)}
|
onClick={() => setIsMinimized(false)}
|
||||||
>
|
>
|
||||||
{/* Avatar */}
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
|
{callState === 'connected' && (
|
||||||
{displayAvatar ? (
|
<div className="absolute inset-0 rounded-xl bg-knot-500/30 animate-call-wave" />
|
||||||
<img src={displayAvatar} alt="" className="relative w-10 h-10 rounded-full object-cover" />
|
|
||||||
) : (
|
|
||||||
<div className="relative w-10 h-10 rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-on-primary font-black text-sm shadow-inner">
|
|
||||||
{initials}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
<Avatar
|
||||||
|
src={displayAvatar ? getMediaUrl(displayAvatar) : null}
|
||||||
|
name={displayName || '?'}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm text-white font-medium truncate max-w-[120px]">{displayName}</p>
|
<p className="text-sm text-white font-medium truncate max-w-[120px]">{displayName}</p>
|
||||||
@@ -1801,7 +1796,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
{(!hasRemoteVideo || remoteIsVideoOffSignal) && !remoteScreenSharing && (
|
{(!hasRemoteVideo || remoteIsVideoOffSignal) && !remoteScreenSharing && (
|
||||||
<div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900/50 backdrop-blur-md z-10">
|
<div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900/50 backdrop-blur-md z-10">
|
||||||
<div className="relative mb-6">
|
<div className="relative mb-6">
|
||||||
<div className="absolute inset-0 rounded-full bg-knot-500/10 animate-pulse" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-knot-500/10 animate-pulse" />
|
||||||
<Avatar
|
<Avatar
|
||||||
src={displayAvatar ? getMediaUrl(displayAvatar) : null}
|
src={displayAvatar ? getMediaUrl(displayAvatar) : null}
|
||||||
name={displayName || '?'}
|
name={displayName || '?'}
|
||||||
@@ -1916,17 +1911,17 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
|||||||
<div className="relative mb-8 mt-4">
|
<div className="relative mb-8 mt-4">
|
||||||
{(callState === 'calling' || callState === 'connected') && (
|
{(callState === 'calling' || callState === 'connected') && (
|
||||||
<>
|
<>
|
||||||
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-knot-500/30 animate-call-wave" />
|
||||||
<div className="absolute inset-0 rounded-full bg-knot-500/20 animate-call-wave-delayed" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-knot-500/20 animate-call-wave-delayed" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{callState === 'incoming' && (
|
{callState === 'incoming' && (
|
||||||
<>
|
<>
|
||||||
<div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-call-wave" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-emerald-500/30 animate-call-wave" />
|
||||||
<div className="absolute inset-0 rounded-full bg-emerald-500/20 animate-call-wave-delayed" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-emerald-500/20 animate-call-wave-delayed" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="relative z-10 p-1.5 rounded-full bg-gradient-to-br from-white/10 to-transparent backdrop-blur-md border border-white/10 shadow-2xl cursor-pointer">
|
<div className="relative z-10 p-1.5 rounded-[1.5rem] bg-gradient-to-br from-white/10 to-transparent backdrop-blur-md border border-white/10 shadow-2xl cursor-pointer">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={displayAvatar ? getMediaUrl(displayAvatar) : null}
|
src={displayAvatar ? getMediaUrl(displayAvatar) : null}
|
||||||
name={displayName || '?'}
|
name={displayName || '?'}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import { useLang } from '../../../../core/infrastructure/i18n';
|
|||||||
interface ParticipantInfo {
|
interface ParticipantInfo {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
userName?: string;
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
avatar?: string | null;
|
avatar?: string | null;
|
||||||
|
avatarUrl?: string | null;
|
||||||
isSharingScreen?: boolean;
|
isSharingScreen?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
isVideoOff?: boolean;
|
isVideoOff?: boolean;
|
||||||
@@ -818,8 +820,8 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
|||||||
onClick={() => setIsMinimized(false)}
|
onClick={() => setIsMinimized(false)}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-0 rounded-full bg-emerald-500/30 animate-call-wave" />
|
<div className="absolute inset-0 rounded-xl bg-emerald-500/30 animate-call-wave" />
|
||||||
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center text-white font-bold text-sm">
|
<div className="relative w-10 h-10 rounded-xl bg-gradient-to-br from-emerald-500 to-teal-600 flex items-center justify-center text-white font-bold text-sm">
|
||||||
{participantList.length + 1}
|
{participantList.length + 1}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -949,7 +951,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
|||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="relative mb-2">
|
<div className="relative mb-2">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={p.avatar ? getMediaUrl(p.avatar) : null}
|
src={(p.avatarUrl || p.avatar) ? getMediaUrl(p.avatarUrl || p.avatar) : null}
|
||||||
name={t('you') || '?'}
|
name={t('you') || '?'}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="shadow-xl"
|
className="shadow-xl"
|
||||||
@@ -968,8 +970,8 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
|||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<div className="relative mb-2">
|
<div className="relative mb-2">
|
||||||
<Avatar
|
<Avatar
|
||||||
src={p.avatar ? getMediaUrl(p.avatar) : null}
|
src={(p.avatarUrl || p.avatar) ? getMediaUrl(p.avatarUrl || p.avatar) : null}
|
||||||
name={p.displayName || p.username || '?'}
|
name={p.displayName || p.userName || p.username || '?'}
|
||||||
size="lg"
|
size="lg"
|
||||||
className="shadow-xl"
|
className="shadow-xl"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import GroupCallModal from '../../calls/presentation/components/GroupCallModal';
|
|||||||
import ContactsSidebar from '../../friends/presentation/components/ContactsSidebar';
|
import ContactsSidebar from '../../friends/presentation/components/ContactsSidebar';
|
||||||
import SettingsPage from '../../users/presentation/components/SettingsPage';
|
import SettingsPage from '../../users/presentation/components/SettingsPage';
|
||||||
import UserProfile from '../../users/presentation/components/UserProfile';
|
import UserProfile from '../../users/presentation/components/UserProfile';
|
||||||
|
import Avatar from '../../../core/presentation/components/ui/Avatar';
|
||||||
|
import { getMediaUrl } from '../../../core/utils/utils';
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
const {
|
const {
|
||||||
@@ -476,14 +478,13 @@ export default function ChatPage() {
|
|||||||
className="bg-zinc-900 border border-white/10 p-8 rounded-3xl w-full max-w-sm flex flex-col items-center shadow-2xl"
|
className="bg-zinc-900 border border-white/10 p-8 rounded-3xl w-full max-w-sm flex flex-col items-center shadow-2xl"
|
||||||
>
|
>
|
||||||
<div className="relative mb-6">
|
<div className="relative mb-6">
|
||||||
<div className="absolute inset-0 rounded-full bg-emerald-500/20 animate-call-wave" />
|
<div className="absolute inset-0 rounded-[1.5rem] bg-emerald-500/20 animate-call-wave" />
|
||||||
<div className="relative w-24 h-24 rounded-full bg-linear-to-br from-primary/80 to-primary flex items-center justify-center text-4xl font-black text-on-primary uppercase overflow-hidden shadow-2xl">
|
<Avatar
|
||||||
{incomingGroupCall.callerInfo?.avatar ? (
|
src={incomingGroupCall.callerInfo?.avatar ? getMediaUrl(incomingGroupCall.callerInfo.avatar) : null}
|
||||||
<img src={incomingGroupCall.callerInfo.avatar} className="w-full h-full object-cover" />
|
name={incomingGroupCall.chatName || '?'}
|
||||||
) : (
|
size="2xl"
|
||||||
<>{incomingGroupCall.chatName.charAt(0)}</>
|
className="relative shadow-2xl"
|
||||||
)}
|
/>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl text-white font-semibold mb-2 text-center break-words w-full max-w-full">
|
<h2 className="text-2xl text-white font-semibold mb-2 text-center break-words w-full max-w-full">
|
||||||
{incomingGroupCall.chatName}
|
{incomingGroupCall.chatName}
|
||||||
|
|||||||
Reference in New Issue
Block a user