Handle expired subscriptions in client profile UI
Build and Deploy Gateway / build-and-push (push) Successful in 21s
Build and Deploy Gateway / deploy (push) Successful in 6s

This commit is contained in:
2026-08-13 15:06:13 +03:00
parent 0ea2f9d548
commit 019930924d
8 changed files with 68 additions and 39 deletions
@@ -378,7 +378,8 @@ function ProfileGroup({
profile: ProfileSnapshot;
renderServerPicker: (profile: ProfileSnapshot, state: ServerPickerRenderState) => ReactNode;
}) {
const expanded = feature.expanded.includes(profile.id);
const expired = profile.subscription.errorCode === 'SUBSCRIPTION_EXPIRED';
const expanded = !expired && feature.expanded.includes(profile.id);
const closing = feature.closing.includes(profile.id);
const visited = feature.visited.includes(profile.id);
const applied = feature.connected
@@ -401,18 +402,29 @@ function ProfileGroup({
const visibleServerLocation = [visibleServer?.city, visibleServer?.country]
.filter((value): value is string => typeof value === 'string' && Boolean(value))
.join(' · ');
const localStatus = profileError?.message || (profile.subscription.status === 'stale'
const localStatus = !expired && (profileError?.message || (profile.subscription.status === 'stale'
? `Последнее обновление не удалось. Данные от ${profile.subscription.fetchedAt
? new Date(profile.subscription.fetchedAt).toLocaleString('ru-RU', { dateStyle: 'short', timeStyle: 'short' })
: 'предыдущей загрузки'}.`
: '');
: ''));
const profileDomain = subscriptionDomain(profile.subscription.host);
const profileDetails = `Серверов: ${profile.servers.length}${profile.subscription.fetchedAt
? ` · обновлено ${new Date(profile.subscription.fetchedAt).toLocaleDateString('ru-RU')}`
: ''}`;
return <section className={`client-profile-group${expanded ? ' is-expanded' : ''}${applied ? ' is-active' : ''}`}>
const profileDetails = expired
? 'Срок действия подписки истёк'
: `Серверов: ${profile.servers.length}${profile.subscription.fetchedAt
? ` · обновлено ${new Date(profile.subscription.fetchedAt).toLocaleDateString('ru-RU')}`
: ''}`;
const profileTitle = <span className="client-profile-title">
<span>
<strong>{profileDomain}</strong>
{(applied || desired) && <em>{applied ? 'АКТИВНА' : 'ВЫБРАНА'}</em>}
</span>
<small>{profileDetails}</small>
</span>;
return <section className={`client-profile-group${expanded ? ' is-expanded' : ''}${applied ? ' is-active' : ''}${expired ? ' is-expired' : ''}`}>
<div className="client-profile-header">
<button
{expired ? <div className="client-profile-disclosure is-static">
{profileTitle}
</div> : <button
className="client-profile-disclosure"
type="button"
aria-expanded={expanded}
@@ -420,15 +432,9 @@ function ProfileGroup({
onClick={() => feature.toggleProfile(profile.id)}
>
<span className="client-profile-chevron" aria-hidden="true"></span>
<span className="client-profile-title">
<span>
<strong>{profileDomain}</strong>
{(applied || desired) && <em>{applied ? 'АКТИВНА' : 'ВЫБРАНА'}</em>}
</span>
<small>{profileDetails}</small>
</span>
</button>
{(applied || desired) && <ProfileUsage profile={profile} />}
{profileTitle}
</button>}
{!expired && (applied || desired) && <ProfileUsage profile={profile} />}
<button
className={`client-profile-refresh${refreshing ? ' is-refreshing' : ''}`}
type="button"
@@ -456,7 +462,7 @@ function ProfileGroup({
{localStatus && <div className="client-profile-local-status" role="status">{localStatus}</div>}
{visibleServer && <button
{!expired && visibleServer && <button
className="client-profile-selected-server"
type="button"
aria-expanded={expanded}
@@ -468,7 +474,7 @@ function ProfileGroup({
{visibleServerLocation && <small>{visibleServerLocation}</small>}
</button>}
{visited && <div
{!expired && visited && <div
id={`client-profile-${profile.id}`}
className={`client-profile-body${expanded ? ' is-open' : ''}${closing ? ' is-closing' : ''}`}
aria-hidden={!expanded}