1
1
<script lang="ts" setup>
2
- import { NCheckbox , NFlex , NText } from ' naive-ui' ;
2
+ import { Config } from ' @dvcol/trakt-http-client/config' ;
3
+ import {
4
+ type CancellablePolling ,
5
+ type TraktDeviceAuthentication ,
6
+ TraktPollingExpiredError ,
7
+ } from ' @dvcol/trakt-http-client/models' ;
8
+ import {
9
+ NButton ,
10
+ NCheckbox ,
11
+ NFlex ,
12
+ NIcon ,
13
+ NInput ,
14
+ NInputGroup ,
15
+ NProgress ,
16
+ NText ,
17
+ } from ' naive-ui' ;
3
18
4
- import { onMounted , ref , Transition , watch } from ' vue' ;
19
+ import { computed , onDeactivated , onMounted , ref , Transition , watch } from ' vue' ;
5
20
6
21
import { useRoute , useRouter } from ' vue-router' ;
7
22
8
23
import GridBackground from ' ~/components/common/background/GridBackground.vue' ;
24
+ import IconClipboard from ' ~/components/icons/IconClipboard.vue' ;
9
25
import LoginCard from ' ~/components/views/login/LoginCard.vue' ;
26
+ import { NotificationService } from ' ~/services/notification.service' ;
10
27
import { TraktService } from ' ~/services/trakt.service' ;
11
28
import { useAuthSettingsStoreRefs } from ' ~/stores/settings/auth.store' ;
29
+ import { useLinksStore } from ' ~/stores/settings/links.store' ;
12
30
import { logger } from ' ~/stores/settings/log.store' ;
13
31
import { useI18n } from ' ~/utils/i18n.utils' ;
14
32
@@ -47,6 +65,75 @@ const onSignIn = async () => {
47
65
logger .error (' Error:' , error );
48
66
}
49
67
};
68
+
69
+ const { openTab } = useLinksStore ();
70
+
71
+ const useCode = ref (false );
72
+ const auth = ref <TraktDeviceAuthentication >();
73
+ const code = computed (() => auth .value ?.user_code );
74
+ const getCodes = async () => {
75
+ try {
76
+ auth .value = await TraktService .device .code ();
77
+ // eslint-disable-next-line @typescript-eslint/no-use-before-define -- recursive call
78
+ await polling ();
79
+ } catch (error ) {
80
+ logger .error (' Failed to login with Trakt.tv' );
81
+ logger .error (error );
82
+ }
83
+ };
84
+
85
+ const poll = ref <CancellablePolling >();
86
+ const progress = ref (0 );
87
+ const progressInterval = ref <ReturnType <typeof setInterval >>();
88
+
89
+ const onCancel = () => {
90
+ if (poll .value ) poll .value .cancel ();
91
+ if (progressInterval .value ) clearInterval (progressInterval .value );
92
+ progress .value = 0 ;
93
+ };
94
+
95
+ const polling = async () => {
96
+ if (! auth .value ) return ;
97
+ if (poll .value ) onCancel ();
98
+ try {
99
+ poll .value = TraktService .device .poll (auth .value );
100
+ progressInterval .value = setInterval (
101
+ () => {
102
+ progress .value += 0.1 ;
103
+ },
104
+ (auth .value .expires_in / 100 ) * 100 ,
105
+ );
106
+ const traktAuth = await poll .value ;
107
+ await TraktService .device .login (traktAuth );
108
+ } catch (error ) {
109
+ logger .error (' Failed to login with Trakt.tv' );
110
+ logger .error (error );
111
+ if (error instanceof TraktPollingExpiredError ) await getCodes ();
112
+ }
113
+ };
114
+
115
+ const copyToClipBoard = () => {
116
+ if (! code .value ?.length ) return ;
117
+ navigator .clipboard .writeText (code .value );
118
+ NotificationService .message .success (i18n (' notification__copied' ));
119
+ };
120
+
121
+ const openVerification = () => {
122
+ const _code = auth .value ?.user_code ;
123
+ openTab (_code ? Config .verification .code (_code ) : Config .verification .url , true );
124
+ };
125
+
126
+ const onCheckedToggle = (checked : boolean ) => {
127
+ if (checked ) return getCodes ();
128
+ onCancel ();
129
+ };
130
+
131
+ const onClick = () => {
132
+ if (useCode .value ) return openVerification ();
133
+ return onSignIn ();
134
+ };
135
+
136
+ onDeactivated (() => onCancel ());
50
137
</script >
51
138
52
139
<template >
@@ -55,18 +142,55 @@ const onSignIn = async () => {
55
142
56
143
<Transition name =" scale" mode =" in-out" >
57
144
<div v-if =" show" >
58
- <LoginCard @on-sign-in =" onSignIn " >
145
+ <LoginCard @on-sign-in =" onClick " >
59
146
<NFlex class =" checkboxes" vertical >
60
- <NCheckbox v-model:checked =" signUp" >
147
+ <NCheckbox v-model:checked =" signUp" :disabled = " useCode " >
61
148
{{ i18n('checkbox__sign_up_for') }}
62
149
<NText type =" info" >{{ i18n('checkbox__new_account') }}</NText >
63
150
!
64
151
</NCheckbox >
65
- <NCheckbox v-model:checked =" useSession" >
152
+ <NCheckbox v-model:checked =" useSession" :disabled = " useCode " >
66
153
{{ i18n('checkbox__use') }}
67
154
<NText type =" info" >{{ i18n('checkbox__active_user') }}</NText >
68
155
{{ i18n('checkbox__session') }}
69
156
</NCheckbox >
157
+
158
+ <NCheckbox v-model:checked =" useCode" @update:checked =" onCheckedToggle" >
159
+ {{ i18n('checkbox__use') }}
160
+ <NText type =" info" >{{ i18n('checkbox__device_code') }}</NText >
161
+ {{ i18n('checkbox__login') }}
162
+ </NCheckbox >
163
+ <div class =" code-input" :class =" { show: useCode }" >
164
+ <NInputGroup class =" input-group" >
165
+ <NInput
166
+ :value =" code"
167
+ placeholder =" Code"
168
+ :disabled =" !code?.length"
169
+ readonly
170
+ />
171
+ <NButton
172
+ tertiary
173
+ type =" primary"
174
+ :disabled =" !code?.length"
175
+ @click =" copyToClipBoard"
176
+ >
177
+ <template #icon >
178
+ <NIcon :component =" IconClipboard" />
179
+ </template >
180
+ </NButton >
181
+ <NProgress
182
+ v-if =" code?.length"
183
+ class =" timeout-code"
184
+ type =" line"
185
+ status =" success"
186
+ :percentage =" progress"
187
+ :show-indicator =" false"
188
+ :theme-overrides =" {
189
+ railHeight: 'var(--rail-height)',
190
+ }"
191
+ />
192
+ </NInputGroup >
193
+ </div >
70
194
</NFlex >
71
195
</LoginCard >
72
196
</div >
@@ -83,4 +207,39 @@ const onSignIn = async () => {
83
207
width : fit-content ;
84
208
margin-bottom : 1.5rem ;
85
209
}
210
+
211
+ .code-input {
212
+ display : flex ;
213
+ align-items : center ;
214
+ justify-content : center ;
215
+ height : 0 ;
216
+ opacity : 0 ;
217
+ scale : 0.95 ;
218
+ transition :
219
+ height 1s ease ,
220
+ scale 0.5s ease ,
221
+ opacity 0.5s ease ;
222
+
223
+ & .show {
224
+ height : 3rem ;
225
+ opacity : 1 ;
226
+ scale : 1 ;
227
+ transition :
228
+ height 0.5s ease ,
229
+ scale 0.5s ease ,
230
+ opacity 0.75s ease ;
231
+ }
232
+
233
+ .input-group {
234
+ position : relative ;
235
+ }
236
+
237
+ .timeout-code {
238
+ --rail-height : 2px ;
239
+
240
+ position : absolute ;
241
+ bottom : calc (var (--rail-height ) * -1 );
242
+ border-radius : 0 ;
243
+ }
244
+ }
86
245
</style >
0 commit comments