Handle expired subscriptions in client profile UI
This commit is contained in:
@@ -482,12 +482,17 @@ export function ClientOverviewPage({
|
||||
&& (canonicalSwitch || localSwitch)
|
||||
&& Boolean(operationProfile && operationServer)
|
||||
&& (operationProfile?.id !== appliedProfile?.id || operationServer?.id !== appliedServer?.id);
|
||||
const subscriptionError = error?.context === 'subscription'
|
||||
&& profiles.some((profile) => profile.id === error.profileId
|
||||
&& profile.subscription.errorCode === 'SUBSCRIPTION_EXPIRED')
|
||||
? null
|
||||
: error;
|
||||
const subscriptionFeature = useSubscriptionFeature({
|
||||
profiles,
|
||||
selection: state.selection,
|
||||
connected,
|
||||
operations: visibleOperations,
|
||||
error,
|
||||
error: subscriptionError,
|
||||
isGateway,
|
||||
gatewayDirect,
|
||||
onAdd: onAddProfile,
|
||||
@@ -716,7 +721,7 @@ export function ClientOverviewPage({
|
||||
<SubscriptionPanel
|
||||
feature={subscriptionFeature}
|
||||
statusSlot={<>
|
||||
<InlineError error={subscriptionFeature.error || error} context="subscription" />
|
||||
<InlineError error={subscriptionFeature.error} context="subscription" />
|
||||
</>}
|
||||
renderServerPicker={(profile, pickerState) => <ServerPicker
|
||||
profileId={profile.id}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -651,7 +651,7 @@
|
||||
}
|
||||
|
||||
.client-profile-body.is-open .client-server-toolbar {
|
||||
animation: client-profile-ping-in 300ms 620ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
animation: client-profile-ping-in 420ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
||||
}
|
||||
|
||||
.client-profile-body.is-closing .client-server-toolbar {
|
||||
|
||||
@@ -105,6 +105,14 @@
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.client-profile-disclosure.is-static {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.client-profile-disclosure.is-static .client-profile-title {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.client-profile-title {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
@@ -139,6 +147,10 @@
|
||||
text-transform: var(--type-label-transform);
|
||||
}
|
||||
|
||||
.client-profile-group.is-expired .client-profile-title small {
|
||||
color: oklch(0.68 0.14 72);
|
||||
}
|
||||
|
||||
.client-profile-title em {
|
||||
padding: 0;
|
||||
color: var(--client-accent);
|
||||
@@ -276,7 +288,7 @@
|
||||
.client-profile-body-inner {
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
padding-bottom: 4px;
|
||||
padding: 0 0 4px 24px;
|
||||
}
|
||||
|
||||
.client-profile-body .client-servers {
|
||||
|
||||
Reference in New Issue
Block a user