Skip to content

Commit 0f20096

Browse files
committed
fix(login): fix progress indicator and add polling indicator
1 parent e2f6e3f commit 0f20096

File tree

3 files changed

+126
-17
lines changed

3 files changed

+126
-17
lines changed

src/components/views/login/LoginCard.vue

+104-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
<script setup lang="ts">
22
import { NButton, NCard, NFlex, NH4 } from 'naive-ui';
33
4-
import type { ButtonProps } from 'naive-ui';
4+
import { computed, onMounted, type PropType, ref, toRefs, watch } from 'vue';
55
6-
import type { PropType } from 'vue';
6+
import type { ButtonProps } from 'naive-ui';
77
88
import Logo from '~/assets/logo.svg';
99
1010
import { useI18n } from '~/utils/i18n.utils';
1111
1212
const i18n = useI18n('login');
1313
14-
defineProps({
14+
const props = defineProps({
1515
logo: {
1616
type: String,
1717
required: false,
@@ -27,6 +27,7 @@ defineProps({
2727
message: {
2828
type: String,
2929
required: false,
30+
default: '',
3031
},
3132
buttonDisabled: {
3233
type: Boolean,
@@ -40,11 +41,59 @@ defineProps({
4041
type: Object as PropType<ButtonProps & Pick<HTMLAnchorElement, 'href'>>,
4142
required: false,
4243
},
44+
minWidth: {
45+
type: String,
46+
required: false,
47+
},
48+
progress: {
49+
type: Number,
50+
required: false,
51+
},
52+
interval: {
53+
type: Number,
54+
required: false,
55+
default: 0,
56+
},
4357
});
4458
4559
const emits = defineEmits<{
4660
(name: 'onSignIn', e: MouseEvent): void;
4761
}>();
62+
63+
const { message, interval, progress } = toRefs(props);
64+
65+
const debounceMessage = ref<string>(message.value);
66+
const _message = computed(() => debounceMessage.value || i18n('sub_title'));
67+
68+
const changed = ref(false);
69+
const timeout = ref<ReturnType<typeof setTimeout>>();
70+
71+
const reverse = ref(false);
72+
const _interval = ref<ReturnType<typeof setInterval>>();
73+
74+
const _progress = computed(() => {
75+
if (interval.value) return reverse.value ? 0 : 100;
76+
return progress?.value;
77+
});
78+
79+
onMounted(() => {
80+
watch(message, (_new, _old) => {
81+
changed.value = _old !== _new;
82+
if (!changed.value) return;
83+
if (timeout.value) clearTimeout(timeout.value);
84+
timeout.value = setTimeout(() => {
85+
changed.value = false;
86+
debounceMessage.value = _new;
87+
}, 250);
88+
});
89+
watch(interval, _new => {
90+
if (_interval.value) clearInterval(_interval.value);
91+
if (!_new) return;
92+
_interval.value = setInterval(() => {
93+
reverse.value = !reverse.value;
94+
}, _new);
95+
});
96+
});
4897
</script>
4998

5099
<template>
@@ -56,7 +105,20 @@ const emits = defineEmits<{
56105

57106
<NFlex class="content" vertical justify="space-evenly">
58107
<slot name="message">
59-
<NH4 class="title" prefix="bar">{{ message ?? i18n('sub_title') }}</NH4>
108+
<NH4
109+
class="title"
110+
:class="{ progress, interval }"
111+
prefix="bar"
112+
:style="{
113+
minWidth: minWidth ?? `${ _message?.length }ch`,
114+
'--progress': `${ _progress }%`,
115+
'--interval': `${interval}ms`,
116+
}"
117+
>
118+
<span class="title-content" :class="{ changed }">
119+
{{ _message }}
120+
</span>
121+
</NH4>
60122
</slot>
61123

62124
<slot name="main" />
@@ -108,12 +170,44 @@ const emits = defineEmits<{
108170
height: 100%;
109171
110172
.title {
173+
--progress-color: rgb(99 226 184 / 10%);
174+
--color: rgb(99 226 184 / 5%);
175+
176+
min-width: 0;
111177
margin: 0;
112178
padding: 1rem;
113179
white-space: pre-line;
114-
background: rgb(99 226 184 / 5%);
115-
transition: background 0.5s;
116-
will-change: background;
180+
background: var(--color);
181+
transition:
182+
min-width 0.5s var(--n-bezier),
183+
background 0.5s var(--n-bezier),
184+
--progress 0.5s var(--n-bezier);
185+
186+
&.progress {
187+
@include mixin.progress-background($rail: var(--color));
188+
}
189+
190+
&.interval {
191+
@include mixin.progress-background(
192+
$rail: var(--color),
193+
$color: var(--progress-color)
194+
);
195+
196+
transition:
197+
min-width 0.5s var(--n-bezier),
198+
background var(--interval) linear,
199+
--progress var(--interval) linear;
200+
}
201+
202+
&-content {
203+
display: flex;
204+
opacity: 1;
205+
transition: opacity 0.25s var(--n-bezier);
206+
207+
&.changed {
208+
opacity: 0.25;
209+
}
210+
}
117211
}
118212
119213
.button {
@@ -125,8 +219,9 @@ const emits = defineEmits<{
125219
}
126220
127221
&:hover {
128-
.title {
129-
background: rgb(99 226 184 / 9%);
222+
.title:not(&.progress) {
223+
--color: rgb(99 226 184 / 9%);
224+
--progress-color: rgb(99 226 184 / 14%);
130225
}
131226
}
132227
}

src/components/views/login/LoginComponent.vue

+18-8
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,15 @@ const getCodes = async () => {
9393
const poll = ref<CancellablePolling>();
9494
const progressInterval = ref<ReturnType<typeof setInterval>>();
9595
const progress = ref(0);
96-
const progressRounded = computed(() => Math.round(progress.value * 10) / 20 || 0.1);
96+
const progressRounded = computed(() => {
97+
if (!auth.value?.expires_in) return 0;
98+
return Math.round((progress.value / auth.value.expires_in) * 100) || 1;
99+
});
100+
101+
const interval = computed(() => {
102+
if (!useCode.value) return 0;
103+
return (auth.value?.interval ?? 0) * 1000;
104+
});
97105
98106
const onCancel = () => {
99107
if (poll.value) poll.value.cancel();
@@ -106,12 +114,9 @@ const polling = async () => {
106114
if (poll.value) onCancel();
107115
try {
108116
poll.value = TraktService.device.poll(auth.value);
109-
progressInterval.value = setInterval(
110-
() => {
111-
progress.value += 0.1;
112-
},
113-
(auth.value.expires_in / 100) * 100,
114-
);
117+
progressInterval.value = setInterval(() => {
118+
progress.value += 1;
119+
}, 1000);
115120
const traktAuth = await poll.value;
116121
await TraktService.device.login(traktAuth);
117122
} catch (error) {
@@ -154,7 +159,12 @@ onDeactivated(() => onCancel());
154159

155160
<Transition name="scale" mode="in-out">
156161
<div v-if="show">
157-
<LoginCard @on-sign-in="onClick">
162+
<LoginCard
163+
:message="useCode ? i18n('polling_title') : undefined"
164+
:interval="interval"
165+
min-width="16.25rem"
166+
@on-sign-in="onClick"
167+
>
158168
<NFlex class="checkboxes" vertical>
159169
<NCheckbox v-model:checked="signUp" :disabled="useCode">
160170
{{ i18n('checkbox__sign_up_for') }}

src/i18n/en/login/login.json

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
"message": "Trakt",
44
"description": "Login title"
55
},
6+
"login__polling_title": {
7+
"message": "Polling for user activation code ...",
8+
"description": "Login polling title"
9+
},
610
"login__sub_title": {
711
"message": "Please sign in to access your data.",
812
"description": "Login sub title"

0 commit comments

Comments
 (0)