@haloduck/ui 2.0.46 → 2.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/haloduck-ui.mjs +446 -10
- package/fesm2022/haloduck-ui.mjs.map +1 -1
- package/haloduck-ui-2.0.48.tgz +0 -0
- package/index.d.ts +52 -4
- package/package.json +1 -1
- package/public/i18n/haloduck/en.json +12 -1
- package/public/i18n/haloduck/ko.json +12 -1
- package/src/tailwind.css +288 -9
- package/haloduck-ui-2.0.46.tgz +0 -0
package/fesm2022/haloduck-ui.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, Input, Component, inject, ChangeDetectorRef, forwardRef, ViewChild, signal, EventEmitter, Output, ViewContainerRef, NgZone, isDevMode, ElementRef, Directive, ChangeDetectionStrategy, HostBinding } from '@angular/core';
|
|
3
|
-
import { signIn, confirmSignIn, resetPassword, confirmResetPassword } from 'aws-amplify/auth';
|
|
3
|
+
import { signIn, confirmSignIn, resetPassword, confirmResetPassword, fetchAuthSession } from 'aws-amplify/auth';
|
|
4
4
|
import * as i1$1 from '@angular/forms';
|
|
5
5
|
import { NG_VALUE_ACCESSOR, Validators, FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
6
6
|
import { BehaviorSubject, zip, Subject, takeUntil, tap, combineLatest, switchMap, of, Observable, distinctUntilChanged, shareReplay, map, take } from 'rxjs';
|
|
@@ -10,13 +10,14 @@ import { CommonModule, DecimalPipe, DatePipe } from '@angular/common';
|
|
|
10
10
|
import * as i2$1 from '@jsverse/transloco';
|
|
11
11
|
import { provideTranslocoScope, TranslocoModule, TranslocoService } from '@jsverse/transloco';
|
|
12
12
|
import * as i2 from '@angular/common/http';
|
|
13
|
+
import * as i1$4 from '@angular/router';
|
|
14
|
+
import { ActivatedRoute, Router, NavigationEnd, RouterLink } from '@angular/router';
|
|
15
|
+
import { Hub } from 'aws-amplify/utils';
|
|
13
16
|
import * as i1$2 from '@angular/cdk/overlay';
|
|
14
17
|
import { Overlay } from '@angular/cdk/overlay';
|
|
15
18
|
import { ComponentPortal } from '@angular/cdk/portal';
|
|
16
19
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
17
20
|
import { CoreService } from '@haloduck/core';
|
|
18
|
-
import * as i1$4 from '@angular/router';
|
|
19
|
-
import { Router, NavigationEnd, RouterLink } from '@angular/router';
|
|
20
21
|
import * as i1$3 from '@angular/platform-browser';
|
|
21
22
|
import { Title } from '@angular/platform-browser';
|
|
22
23
|
import * as THREE from 'three';
|
|
@@ -199,6 +200,14 @@ class AuthenticateComponent {
|
|
|
199
200
|
fb;
|
|
200
201
|
http;
|
|
201
202
|
notificationService = inject(NotificationService);
|
|
203
|
+
metaData;
|
|
204
|
+
socialLoginProviders = [
|
|
205
|
+
{ name: 'google', enabled: true },
|
|
206
|
+
{ name: 'apple', enabled: true },
|
|
207
|
+
{ name: 'kakao', enabled: true },
|
|
208
|
+
];
|
|
209
|
+
showSocialLogin = true;
|
|
210
|
+
showDivider = true;
|
|
202
211
|
loginForm;
|
|
203
212
|
signupForm;
|
|
204
213
|
resetForm;
|
|
@@ -237,6 +246,7 @@ class AuthenticateComponent {
|
|
|
237
246
|
password: this.loginForm.value.password,
|
|
238
247
|
options: {
|
|
239
248
|
authFlowType: 'USER_PASSWORD_AUTH',
|
|
249
|
+
clientMetadata: this.metaData,
|
|
240
250
|
},
|
|
241
251
|
})
|
|
242
252
|
.then((res) => {
|
|
@@ -261,6 +271,9 @@ class AuthenticateComponent {
|
|
|
261
271
|
confirmNewPassword() {
|
|
262
272
|
confirmSignIn({
|
|
263
273
|
challengeResponse: this.newPasswordForm.value.newPassword,
|
|
274
|
+
options: {
|
|
275
|
+
clientMetadata: this.metaData,
|
|
276
|
+
},
|
|
264
277
|
})
|
|
265
278
|
.then(() => {
|
|
266
279
|
this.notificationService.showNotification('success', 'Successfully signed in.', 3000);
|
|
@@ -281,7 +294,10 @@ class AuthenticateComponent {
|
|
|
281
294
|
this.http.post('/api/signup', this.signupForm.value).subscribe(console.log);
|
|
282
295
|
}
|
|
283
296
|
reset() {
|
|
284
|
-
resetPassword({
|
|
297
|
+
resetPassword({
|
|
298
|
+
username: this.resetForm.value.email,
|
|
299
|
+
options: { clientMetadata: this.metaData },
|
|
300
|
+
})
|
|
285
301
|
.then(() => {
|
|
286
302
|
this.switchStage('otpVerify');
|
|
287
303
|
})
|
|
@@ -294,6 +310,7 @@ class AuthenticateComponent {
|
|
|
294
310
|
username: this.resetForm.value.email,
|
|
295
311
|
confirmationCode: this.otpForm.value.code,
|
|
296
312
|
newPassword: this.otpForm.value.newPassword,
|
|
313
|
+
options: { clientMetadata: this.metaData },
|
|
297
314
|
})
|
|
298
315
|
.then(() => {
|
|
299
316
|
this.notificationService.showNotification('success', 'Successfully reset password.', 3000);
|
|
@@ -337,13 +354,432 @@ class AuthenticateComponent {
|
|
|
337
354
|
{ label: 'one special character', valid: this.hasSpecialChar },
|
|
338
355
|
];
|
|
339
356
|
}
|
|
357
|
+
// Social Login Methods
|
|
358
|
+
get enabledSocialProviders() {
|
|
359
|
+
return this.socialLoginProviders.filter((provider) => provider.enabled);
|
|
360
|
+
}
|
|
361
|
+
isProviderEnabled(providerName) {
|
|
362
|
+
return this.socialLoginProviders.some((provider) => provider.name === providerName && provider.enabled);
|
|
363
|
+
}
|
|
364
|
+
loginWithGoogle() {
|
|
365
|
+
try {
|
|
366
|
+
// Google 로그인 로직 구현
|
|
367
|
+
// 실제 구현시에는 Google OAuth2 SDK 또는 AWS Amplify의 Google 제공자를 사용
|
|
368
|
+
console.log('Google login initiated');
|
|
369
|
+
// 예시: window.location을 사용한 OAuth 리다이렉션
|
|
370
|
+
const googleProvider = this.socialLoginProviders.find((p) => p.name === 'google');
|
|
371
|
+
if (googleProvider?.clientId) {
|
|
372
|
+
const redirectUrl = googleProvider.redirectUrl || window.location.origin;
|
|
373
|
+
const googleAuthUrl = `https://accounts.google.com/oauth/authorize?client_id=${googleProvider.clientId}&redirect_uri=${redirectUrl}&scope=openid%20email%20profile&response_type=code`;
|
|
374
|
+
window.location.href = googleAuthUrl;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
this.notificationService.showNotification('error', 'Google login configuration missing.');
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
this.notificationService.showNotification('error', 'Google login failed.');
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
loginWithApple() {
|
|
385
|
+
try {
|
|
386
|
+
// Apple 로그인 로직 구현
|
|
387
|
+
// 실제 구현시에는 Apple Sign In JS SDK를 사용
|
|
388
|
+
console.log('Apple login initiated');
|
|
389
|
+
const appleProvider = this.socialLoginProviders.find((p) => p.name === 'apple');
|
|
390
|
+
if (appleProvider?.clientId) {
|
|
391
|
+
const redirectUrl = appleProvider.redirectUrl || window.location.origin;
|
|
392
|
+
const appleAuthUrl = `https://appleid.apple.com/auth/authorize?client_id=${appleProvider.clientId}&redirect_uri=${redirectUrl}&scope=name%20email&response_type=code&response_mode=form_post`;
|
|
393
|
+
window.location.href = appleAuthUrl;
|
|
394
|
+
}
|
|
395
|
+
else {
|
|
396
|
+
this.notificationService.showNotification('error', 'Apple login configuration missing.');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (error) {
|
|
400
|
+
this.notificationService.showNotification('error', 'Apple login failed.');
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
loginWithKakao() {
|
|
404
|
+
try {
|
|
405
|
+
// KakaoTalk 로그인 로직 구현
|
|
406
|
+
// 실제 구현시에는 Kakao JavaScript SDK를 사용
|
|
407
|
+
console.log('Kakao login initiated');
|
|
408
|
+
const kakaoProvider = this.socialLoginProviders.find((p) => p.name === 'kakao');
|
|
409
|
+
if (kakaoProvider?.clientId) {
|
|
410
|
+
const redirectUrl = kakaoProvider.redirectUrl || window.location.origin;
|
|
411
|
+
const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${kakaoProvider.clientId}&redirect_uri=${redirectUrl}&response_type=code&state=${encodeURIComponent(JSON.stringify(kakaoProvider.state || {}))}`;
|
|
412
|
+
window.location.href = kakaoAuthUrl;
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.notificationService.showNotification('error', 'Kakao login configuration missing.');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
this.notificationService.showNotification('error', 'Kakao login failed.');
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
getSocialLoginIcon(provider) {
|
|
423
|
+
const icons = {
|
|
424
|
+
google: 'M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z',
|
|
425
|
+
apple: 'M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z',
|
|
426
|
+
kakao: 'M12 3C7.03 3 3 6.17 3 10.08c0 2.53 1.67 4.74 4.19 6.08L6.45 18.9c-.14.22-.02.52.24.52.08 0 .17-.03.23-.09L9.2 17.4c.87.17 1.8.26 2.8.26s1.93-.09 2.8-.26l2.28 1.93c.06.06.15.09.23.09.26 0 .38-.3.24-.52l-.74-2.74C19.33 14.82 21 12.61 21 10.08 21 6.17 16.97 3 12 3z',
|
|
427
|
+
};
|
|
428
|
+
return icons[provider] || '';
|
|
429
|
+
}
|
|
430
|
+
getSocialLoginLabel(provider) {
|
|
431
|
+
const labels = {
|
|
432
|
+
google: 'Continue with Google',
|
|
433
|
+
apple: 'Continue with Apple',
|
|
434
|
+
kakao: 'Continue with KakaoTalk',
|
|
435
|
+
};
|
|
436
|
+
return labels[provider] || `Continue with ${provider}`;
|
|
437
|
+
}
|
|
340
438
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, deps: [{ token: i1$1.FormBuilder }, { token: i2.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
341
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: AuthenticateComponent, isStandalone: true, selector: "haloduck-authenticate", ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div\n class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\"\n >\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"login()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button\n data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\"\n >Sign In</haloduck-button\n >\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"signup()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button data-testid=\"authenticate-signup-submit\" type=\"submit\" variant=\"primary\"\n >Sign Up</haloduck-button\n >\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\" (ngSubmit)=\"reset()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-button\n data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\"\n >Send Verification Code</haloduck-button\n >\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\" (ngSubmit)=\"verifyOtp()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\"\n />\n <haloduck-input\n data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n />\n <haloduck-button\n data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\"\n >Reset Password</haloduck-button\n >\n </form>\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form\n [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\"\n >\n <haloduck-input\n data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n >\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span\n [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\"\n >\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button\n data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\"\n >Change Password</haloduck-button\n >\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button\n data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\"\n >to Sign In</haloduck-button\n >\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button\n data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\"\n >Reset Password</haloduck-button\n >\n }\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: InputComponent, selector: "haloduck-input", inputs: ["placeholder", "type", "disabled", "rows", "autofocus", "value"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }] });
|
|
439
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: AuthenticateComponent, isStandalone: true, selector: "haloduck-authenticate", inputs: { metaData: "metaData", socialLoginProviders: "socialLoginProviders", showSocialLogin: "showSocialLogin", showDivider: "showDivider" }, ngImport: i0, template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div\n class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\"\n >\n @switch (stage) {\n @case ('login') {\n <!-- \uC18C\uC15C \uB85C\uADF8\uC778 \uBC84\uD2BC\uB4E4 -->\n @if (showSocialLogin && enabledSocialProviders.length > 0) {\n <div class=\"mb-6\">\n <div class=\"space-y-3\">\n <!-- Google \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('google')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-google\"\n (click)=\"loginWithGoogle()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-gray-300 rounded-lg shadow-sm bg-white hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Google SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n fill=\"#EA4335\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('google') }}</span>\n </button>\n }\n\n <!-- Apple \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('apple')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-apple\"\n (click)=\"loginWithApple()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-black rounded-lg shadow-sm bg-black hover:bg-gray-900 dark:bg-black dark:hover:bg-gray-900 text-white transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Apple SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-white\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('apple') }}</span>\n </button>\n }\n\n <!-- Kakao \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('kakao')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-kakao\"\n (click)=\"loginWithKakao()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-yellow-400 rounded-lg shadow-sm bg-yellow-400 hover:bg-yellow-500 text-black transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Kakao SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-black\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 3C7.03 3 3 6.17 3 10.08c0 2.53 1.67 4.74 4.19 6.08L6.45 18.9c-.14.22-.02.52.24.52.08 0 .17-.03.23-.09L9.2 17.4c.87.17 1.8.26 2.8.26s1.93-.09 2.8-.26l2.28 1.93c.06.06.15.09.23.09.26 0 .38-.3.24-.52l-.74-2.74C19.33 14.82 21 12.61 21 10.08 21 6.17 16.97 3 12 3z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('kakao') }}</span>\n </button>\n }\n </div>\n\n <!-- \uAD6C\uBD84\uC120 -->\n @if (showDivider) {\n <div class=\"relative my-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-gray-300 dark:border-gray-600\"></div>\n </div>\n <div class=\"relative flex justify-center text-sm\">\n <span\n class=\"px-2 bg-light-background dark:bg-dark-background text-gray-500 dark:text-gray-400\"\n >or</span\n >\n </div>\n </div>\n }\n </div>\n }\n\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"login()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button\n data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\"\n >Sign In</haloduck-button\n >\n </form>\n }\n @case ('signup') {\n <!-- \uC18C\uC15C \uB85C\uADF8\uC778 \uBC84\uD2BC\uB4E4 (\uD68C\uC6D0\uAC00\uC785) -->\n @if (showSocialLogin && enabledSocialProviders.length > 0) {\n <div class=\"mb-6\">\n <div class=\"space-y-3\">\n <!-- Google \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('google')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-google\"\n (click)=\"loginWithGoogle()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-gray-300 rounded-lg shadow-sm bg-white hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Google SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n fill=\"#EA4335\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('google') }}</span>\n </button>\n }\n\n <!-- Apple \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('apple')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-apple\"\n (click)=\"loginWithApple()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-black rounded-lg shadow-sm bg-black hover:bg-gray-900 dark:bg-black dark:hover:bg-gray-900 text-white transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Apple SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-white\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('apple') }}</span>\n </button>\n }\n\n <!-- Kakao \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('kakao')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-kakao\"\n (click)=\"loginWithKakao()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-yellow-400 rounded-lg shadow-sm bg-yellow-400 hover:bg-yellow-500 text-black transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Kakao SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-black\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 3C7.03 3 3 6.17 3 10.08c0 2.53 1.67 4.74 4.19 6.08L6.45 18.9c-.14.22-.02.52.24.52.08 0 .17-.03.23-.09L9.2 17.4c.87.17 1.8.26 2.8.26s1.93-.09 2.8-.26l2.28 1.93c.06.06.15.09.23.09.26 0 .38-.3.24-.52l-.74-2.74C19.33 14.82 21 12.61 21 10.08 21 6.17 16.97 3 12 3z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('kakao') }}</span>\n </button>\n }\n </div>\n\n <!-- \uAD6C\uBD84\uC120 -->\n @if (showDivider) {\n <div class=\"relative my-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-gray-300 dark:border-gray-600\"></div>\n </div>\n <div class=\"relative flex justify-center text-sm\">\n <span\n class=\"px-2 bg-light-background dark:bg-dark-background text-gray-500 dark:text-gray-400\"\n >or</span\n >\n </div>\n </div>\n }\n </div>\n }\n\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"signup()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button data-testid=\"authenticate-signup-submit\" type=\"submit\" variant=\"primary\"\n >Sign Up</haloduck-button\n >\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\" (ngSubmit)=\"reset()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-button\n data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\"\n >Send Verification Code</haloduck-button\n >\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\" (ngSubmit)=\"verifyOtp()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\"\n />\n <haloduck-input\n data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n />\n <haloduck-button\n data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\"\n >Reset Password</haloduck-button\n >\n </form>\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form\n [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\"\n >\n <haloduck-input\n data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n >\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span\n [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\"\n >\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button\n data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\"\n >Change Password</haloduck-button\n >\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex flex-col items-center mt-6 gap-4\">\n <div class=\"flex justify-center gap-4\">\n @if (stage !== 'login') {\n <haloduck-button\n data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\"\n >to Sign In</haloduck-button\n >\n }\n @if (stage !== 'signup') {\n <haloduck-button\n data-testid=\"authenticate-to-signup\"\n variant=\"none\"\n (click)=\"switchStage('signup')\"\n >to Sign Up</haloduck-button\n >\n }\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button\n data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\"\n >Reset Password</haloduck-button\n >\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1$1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: InputComponent, selector: "haloduck-input", inputs: ["placeholder", "type", "disabled", "rows", "autofocus", "value"] }, { kind: "component", type: ButtonComponent, selector: "haloduck-button", inputs: ["disabled", "variant"] }, { kind: "ngmodule", type: CommonModule }] });
|
|
342
440
|
}
|
|
343
441
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateComponent, decorators: [{
|
|
344
442
|
type: Component,
|
|
345
|
-
args: [{ selector: 'haloduck-authenticate', imports: [FormsModule, ReactiveFormsModule, InputComponent, ButtonComponent], template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div\n class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\"\n >\n @switch (stage) {\n @case ('login') {\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"login()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button\n data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\"\n >Sign In</haloduck-button\n >\n </form>\n }\n @case ('signup') {\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"signup()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button data-testid=\"authenticate-signup-submit\" type=\"submit\" variant=\"primary\"\n >Sign Up</haloduck-button\n >\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\" (ngSubmit)=\"reset()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-button\n data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\"\n >Send Verification Code</haloduck-button\n >\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\" (ngSubmit)=\"verifyOtp()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\"\n />\n <haloduck-input\n data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n />\n <haloduck-button\n data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\"\n >Reset Password</haloduck-button\n >\n </form>\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form\n [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\"\n >\n <haloduck-input\n data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n >\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span\n [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\"\n >\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button\n data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\"\n >Change Password</haloduck-button\n >\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex justify-center mt-6 gap-4\">\n @if (stage !== 'login') {\n <haloduck-button\n data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\"\n >to Sign In</haloduck-button\n >\n }\n <!-- <haloduck-button class=\"text-sm text-blue-600 hover:underline\"\n (click)=\"switchStage('signup')\">\uD68C\uC6D0\uAC00\uC785</haloduck-button> -->\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button\n data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\"\n >Reset Password</haloduck-button\n >\n }\n </div>\n </div>\n</div>\n" }]
|
|
346
|
-
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2.HttpClient }]
|
|
443
|
+
args: [{ selector: 'haloduck-authenticate', imports: [FormsModule, ReactiveFormsModule, InputComponent, ButtonComponent, CommonModule], template: "<div class=\"flex flex-col items-center justify-center h-full\">\n <!-- auth.component.html -->\n <div\n class=\"w-full max-w-md mx-auto mt-20 p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background\"\n >\n @switch (stage) {\n @case ('login') {\n <!-- \uC18C\uC15C \uB85C\uADF8\uC778 \uBC84\uD2BC\uB4E4 -->\n @if (showSocialLogin && enabledSocialProviders.length > 0) {\n <div class=\"mb-6\">\n <div class=\"space-y-3\">\n <!-- Google \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('google')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-google\"\n (click)=\"loginWithGoogle()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-gray-300 rounded-lg shadow-sm bg-white hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Google SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n fill=\"#EA4335\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('google') }}</span>\n </button>\n }\n\n <!-- Apple \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('apple')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-apple\"\n (click)=\"loginWithApple()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-black rounded-lg shadow-sm bg-black hover:bg-gray-900 dark:bg-black dark:hover:bg-gray-900 text-white transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Apple SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-white\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('apple') }}</span>\n </button>\n }\n\n <!-- Kakao \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('kakao')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-social-kakao\"\n (click)=\"loginWithKakao()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-yellow-400 rounded-lg shadow-sm bg-yellow-400 hover:bg-yellow-500 text-black transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Kakao SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-black\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 3C7.03 3 3 6.17 3 10.08c0 2.53 1.67 4.74 4.19 6.08L6.45 18.9c-.14.22-.02.52.24.52.08 0 .17-.03.23-.09L9.2 17.4c.87.17 1.8.26 2.8.26s1.93-.09 2.8-.26l2.28 1.93c.06.06.15.09.23.09.26 0 .38-.3.24-.52l-.74-2.74C19.33 14.82 21 12.61 21 10.08 21 6.17 16.97 3 12 3z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('kakao') }}</span>\n </button>\n }\n </div>\n\n <!-- \uAD6C\uBD84\uC120 -->\n @if (showDivider) {\n <div class=\"relative my-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-gray-300 dark:border-gray-600\"></div>\n </div>\n <div class=\"relative flex justify-center text-sm\">\n <span\n class=\"px-2 bg-light-background dark:bg-dark-background text-gray-500 dark:text-gray-400\"\n >or</span\n >\n </div>\n </div>\n }\n </div>\n }\n\n <!-- \uB85C\uADF8\uC778 -->\n <form [formGroup]=\"loginForm\" (ngSubmit)=\"login()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-login-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-login-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button\n data-testid=\"authenticate-login-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"login()\"\n >Sign In</haloduck-button\n >\n </form>\n }\n @case ('signup') {\n <!-- \uC18C\uC15C \uB85C\uADF8\uC778 \uBC84\uD2BC\uB4E4 (\uD68C\uC6D0\uAC00\uC785) -->\n @if (showSocialLogin && enabledSocialProviders.length > 0) {\n <div class=\"mb-6\">\n <div class=\"space-y-3\">\n <!-- Google \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('google')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-google\"\n (click)=\"loginWithGoogle()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-gray-300 rounded-lg shadow-sm bg-white hover:bg-gray-50 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Google SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z\"\n fill=\"#4285F4\"\n />\n <path\n d=\"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z\"\n fill=\"#34A853\"\n />\n <path\n d=\"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z\"\n fill=\"#FBBC05\"\n />\n <path\n d=\"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z\"\n fill=\"#EA4335\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('google') }}</span>\n </button>\n }\n\n <!-- Apple \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('apple')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-apple\"\n (click)=\"loginWithApple()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-black rounded-lg shadow-sm bg-black hover:bg-gray-900 dark:bg-black dark:hover:bg-gray-900 text-white transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Apple SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-white\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('apple') }}</span>\n </button>\n }\n\n <!-- Kakao \uB85C\uADF8\uC778 \uBC84\uD2BC -->\n @if (isProviderEnabled('kakao')) {\n <button\n type=\"button\"\n data-testid=\"authenticate-signup-social-kakao\"\n (click)=\"loginWithKakao()\"\n class=\"w-full flex items-center justify-center gap-3 px-4 py-3 border border-yellow-400 rounded-lg shadow-sm bg-yellow-400 hover:bg-yellow-500 text-black transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 hover:cursor-pointer\"\n >\n <!-- Kakao SVG \uC544\uC774\uCF58 -->\n <svg class=\"w-5 h-5 text-black\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path\n d=\"M12 3C7.03 3 3 6.17 3 10.08c0 2.53 1.67 4.74 4.19 6.08L6.45 18.9c-.14.22-.02.52.24.52.08 0 .17-.03.23-.09L9.2 17.4c.87.17 1.8.26 2.8.26s1.93-.09 2.8-.26l2.28 1.93c.06.06.15.09.23.09.26 0 .38-.3.24-.52l-.74-2.74C19.33 14.82 21 12.61 21 10.08 21 6.17 16.97 3 12 3z\"\n />\n </svg>\n <span class=\"font-medium text-sm\">{{ getSocialLoginLabel('kakao') }}</span>\n </button>\n }\n </div>\n\n <!-- \uAD6C\uBD84\uC120 -->\n @if (showDivider) {\n <div class=\"relative my-6\">\n <div class=\"absolute inset-0 flex items-center\">\n <div class=\"w-full border-t border-gray-300 dark:border-gray-600\"></div>\n </div>\n <div class=\"relative flex justify-center text-sm\">\n <span\n class=\"px-2 bg-light-background dark:bg-dark-background text-gray-500 dark:text-gray-400\"\n >or</span\n >\n </div>\n </div>\n }\n </div>\n }\n\n <!-- \uD68C\uC6D0\uAC00\uC785 -->\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"signup()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-signup-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-input\n data-testid=\"authenticate-signup-password\"\n type=\"password\"\n formControlName=\"password\"\n placeholder=\"Password\"\n />\n <haloduck-button data-testid=\"authenticate-signup-submit\" type=\"submit\" variant=\"primary\"\n >Sign Up</haloduck-button\n >\n </form>\n }\n @case ('reset') {\n <!-- \uBE44\uBC00\uBC88\uD638 \uC7AC\uC124\uC815 \uC694\uCCAD -->\n <form [formGroup]=\"resetForm\" (ngSubmit)=\"reset()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-reset-email\"\n type=\"email\"\n formControlName=\"email\"\n placeholder=\"Email\"\n />\n <haloduck-button\n data-testid=\"authenticate-reset-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"reset()\"\n >Send Verification Code</haloduck-button\n >\n </form>\n }\n @case ('otpVerify') {\n <!-- OTP \uC778\uC99D \uD6C4 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uC785\uB825 -->\n <form [formGroup]=\"otpForm\" (ngSubmit)=\"verifyOtp()\" class=\"flex flex-col gap-4\">\n <haloduck-input\n data-testid=\"authenticate-otp-code\"\n type=\"text\"\n formControlName=\"code\"\n placeholder=\"Verification Code\"\n />\n <haloduck-input\n data-testid=\"authenticate-otp-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n />\n <haloduck-button\n data-testid=\"authenticate-otp-submit\"\n type=\"submit\"\n variant=\"primary\"\n (click)=\"verifyOtp()\"\n >Reset Password</haloduck-button\n >\n </form>\n }\n @case ('newPassword') {\n <!-- \uC784\uC2DC \uBE44\uBC00\uBC88\uD638 \u2192 \uC0C8 \uBE44\uBC00\uBC88\uD638 \uBCC0\uACBD -->\n <form\n [formGroup]=\"newPasswordForm\"\n (ngSubmit)=\"confirmNewPassword()\"\n class=\"flex flex-col gap-4\"\n >\n <haloduck-input\n data-testid=\"authenticate-new-password-password\"\n type=\"password\"\n formControlName=\"newPassword\"\n placeholder=\"New password\"\n >\n </haloduck-input>\n <div class=\"text-sm flex flex-col\">\n @for (rule of passwordRules; track rule.label) {\n <span\n [class.text-light-secondary]=\"rule.valid && passwordValue !== ''\"\n [class.line-through]=\"rule.valid && passwordValue !== ''\"\n [class.text-light-danger]=\"!rule.valid && passwordValue !== ''\"\n [class.text-light-inactive]=\"passwordValue === ''\"\n >\n {{ rule.valid ? '\u2714' : '\u2717' }} {{ rule.label }}\n </span>\n }\n </div>\n <haloduck-button\n data-testid=\"authenticate-new-password-submit\"\n type=\"submit\"\n (click)=\"confirmNewPassword()\"\n variant=\"primary\"\n [disabled]=\"!isPasswordValid\"\n >Change Password</haloduck-button\n >\n </form>\n }\n }\n\n <!-- \uB2E8\uACC4 \uC804\uD658 \uD0ED -->\n <div class=\"flex flex-col items-center mt-6 gap-4\">\n <div class=\"flex justify-center gap-4\">\n @if (stage !== 'login') {\n <haloduck-button\n data-testid=\"authenticate-to-signin\"\n variant=\"none\"\n (click)=\"switchStage('login')\"\n >to Sign In</haloduck-button\n >\n }\n @if (stage !== 'signup') {\n <haloduck-button\n data-testid=\"authenticate-to-signup\"\n variant=\"none\"\n (click)=\"switchStage('signup')\"\n >to Sign Up</haloduck-button\n >\n }\n @if (stage !== 'reset' && stage !== 'otpVerify' && stage !== 'newPassword') {\n <haloduck-button\n data-testid=\"authenticate-to-reset-password\"\n variant=\"secondary\"\n (click)=\"switchStage('reset')\"\n >Reset Password</haloduck-button\n >\n }\n </div>\n </div>\n </div>\n</div>\n" }]
|
|
444
|
+
}], ctorParameters: () => [{ type: i1$1.FormBuilder }, { type: i2.HttpClient }], propDecorators: { metaData: [{
|
|
445
|
+
type: Input
|
|
446
|
+
}], socialLoginProviders: [{
|
|
447
|
+
type: Input
|
|
448
|
+
}], showSocialLogin: [{
|
|
449
|
+
type: Input
|
|
450
|
+
}], showDivider: [{
|
|
451
|
+
type: Input
|
|
452
|
+
}] } });
|
|
453
|
+
|
|
454
|
+
class AuthenticateCallbackComponent {
|
|
455
|
+
route = inject(ActivatedRoute);
|
|
456
|
+
router = inject(Router);
|
|
457
|
+
notificationService = inject(NotificationService);
|
|
458
|
+
isProcessing = true;
|
|
459
|
+
error = null;
|
|
460
|
+
success = false;
|
|
461
|
+
ngOnInit() {
|
|
462
|
+
this.handleSocialCallback();
|
|
463
|
+
}
|
|
464
|
+
async handleSocialCallback() {
|
|
465
|
+
try {
|
|
466
|
+
// URL의 쿼리 파라미터에서 data 추출
|
|
467
|
+
const queryParams = this.route.snapshot.queryParams;
|
|
468
|
+
const encodedData = queryParams['data'];
|
|
469
|
+
if (!encodedData) {
|
|
470
|
+
throw new Error('ui.authenticate.Social login data not found.');
|
|
471
|
+
}
|
|
472
|
+
// Base64 디코딩
|
|
473
|
+
const decodedData = this.decodeBase64(encodedData);
|
|
474
|
+
const cognitoUserResponse = JSON.parse(decodedData);
|
|
475
|
+
// Amplify 로그인 처리
|
|
476
|
+
await this.processAmplifyLogin(cognitoUserResponse);
|
|
477
|
+
this.success = true;
|
|
478
|
+
this.isProcessing = false;
|
|
479
|
+
this.notificationService.showNotification('success', '소셜 로그인이 완료되었습니다.', 3000);
|
|
480
|
+
// 로그인 성공 후 리다이렉트
|
|
481
|
+
setTimeout(() => {
|
|
482
|
+
this.redirectAfterLogin();
|
|
483
|
+
}, 500);
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
console.error('Social callback error:', error);
|
|
487
|
+
this.error = error instanceof Error ? error.message : '로그인 처리 중 오류가 발생했습니다.';
|
|
488
|
+
this.isProcessing = false;
|
|
489
|
+
this.notificationService.showNotification('error', this.error);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
decodeBase64(encodedString) {
|
|
493
|
+
try {
|
|
494
|
+
// URL-safe base64 디코딩을 위해 문자 치환
|
|
495
|
+
const base64String = encodedString.replace(/-/g, '+').replace(/_/g, '/');
|
|
496
|
+
// 패딩 추가 (필요한 경우)
|
|
497
|
+
const padding = base64String.length % 4;
|
|
498
|
+
const paddedString = padding ? base64String + '='.repeat(4 - padding) : base64String;
|
|
499
|
+
return atob(paddedString);
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
throw new Error('ui.authenticate.Invalid data format.');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
async processAmplifyLogin(cognitoUserResponse) {
|
|
506
|
+
try {
|
|
507
|
+
console.log('Processing Amplify login with:', {
|
|
508
|
+
email: cognitoUserResponse.email,
|
|
509
|
+
hasAuthResult: !!cognitoUserResponse.authResult,
|
|
510
|
+
});
|
|
511
|
+
if (cognitoUserResponse.authResult) {
|
|
512
|
+
const { AccessToken, IdToken, RefreshToken } = cognitoUserResponse.authResult;
|
|
513
|
+
// Cognito 토큰을 로컬스토리지에 저장 (Amplify가 사용할 수 있는 형태로)
|
|
514
|
+
const keyPrefix = `CognitoIdentityServiceProvider.${await this.getClientId()}`;
|
|
515
|
+
const username = cognitoUserResponse.email;
|
|
516
|
+
// Amplify가 인식할 수 있는 형태로 토큰들을 저장
|
|
517
|
+
localStorage.setItem(`${keyPrefix}.${username}.accessToken`, AccessToken);
|
|
518
|
+
localStorage.setItem(`${keyPrefix}.${username}.idToken`, IdToken);
|
|
519
|
+
localStorage.setItem(`${keyPrefix}.${username}.refreshToken`, RefreshToken);
|
|
520
|
+
localStorage.setItem(`${keyPrefix}.LastAuthUser`, username);
|
|
521
|
+
localStorage.setItem(`${keyPrefix}.${username}.tokenScopesAccepted`, '1');
|
|
522
|
+
// Amplify Hub를 통해 인증 상태 변경 알림
|
|
523
|
+
Hub.dispatch('auth', {
|
|
524
|
+
event: 'signedIn',
|
|
525
|
+
data: {
|
|
526
|
+
username: cognitoUserResponse.email,
|
|
527
|
+
signInDetails: {
|
|
528
|
+
loginId: cognitoUserResponse.email,
|
|
529
|
+
authFlowType: 'CUSTOM_AUTH',
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
// 세션 새로고침을 통해 Amplify가 토큰을 인식하도록 함
|
|
534
|
+
try {
|
|
535
|
+
await fetchAuthSession({ forceRefresh: true });
|
|
536
|
+
}
|
|
537
|
+
catch (sessionError) {
|
|
538
|
+
console.warn('Session refresh failed, but tokens are stored:', sessionError);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
console.error('Amplify login processing error:', error);
|
|
544
|
+
throw new Error('ui.authenticate.Authentication processing failed.');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
async getClientId() {
|
|
548
|
+
// Amplify 설정에서 클라이언트 ID를 가져오는 로직
|
|
549
|
+
// 실제 환경에서는 Amplify.configure에서 설정된 값을 사용
|
|
550
|
+
try {
|
|
551
|
+
const { Amplify } = await import('aws-amplify');
|
|
552
|
+
const config = Amplify.getConfig();
|
|
553
|
+
return config.Auth?.Cognito?.userPoolClientId || 'default-client-id';
|
|
554
|
+
}
|
|
555
|
+
catch {
|
|
556
|
+
// fallback - 환경변수나 설정파일에서 가져올 수 있습니다
|
|
557
|
+
return 'default-client-id';
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
redirectToLogin() {
|
|
561
|
+
// 로그인 페이지로 리다이렉트 (라우트는 실제 애플리케이션에 맞게 조정)
|
|
562
|
+
this.router.navigate(['/auth/login']);
|
|
563
|
+
}
|
|
564
|
+
redirectAfterLogin() {
|
|
565
|
+
// 로그인 성공 후 리다이렉트할 페이지
|
|
566
|
+
// returnUrl이 있으면 그곳으로, 없으면 기본 페이지로
|
|
567
|
+
const returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
|
|
568
|
+
this.router.navigate([returnUrl]);
|
|
569
|
+
}
|
|
570
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateCallbackComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
571
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.1.4", type: AuthenticateCallbackComponent, isStandalone: true, selector: "haloduck-authenticate-callback", providers: [provideTranslocoScope('haloduck')], ngImport: i0, template: `
|
|
572
|
+
<div
|
|
573
|
+
class="flex flex-col items-center justify-center min-h-screen bg-light-background dark:bg-dark-background"
|
|
574
|
+
>
|
|
575
|
+
<div
|
|
576
|
+
class="w-full max-w-md mx-auto p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background"
|
|
577
|
+
>
|
|
578
|
+
<div class="text-center">
|
|
579
|
+
@if (isProcessing) {
|
|
580
|
+
<div class="space-y-6">
|
|
581
|
+
<div class="flex justify-center">
|
|
582
|
+
<div
|
|
583
|
+
class="animate-spin rounded-full h-16 w-16 border-4 border-gray-300 border-t-blue-600 dark:border-gray-600 dark:border-t-blue-400"
|
|
584
|
+
></div>
|
|
585
|
+
</div>
|
|
586
|
+
<div class="space-y-2">
|
|
587
|
+
<h2
|
|
588
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
589
|
+
>
|
|
590
|
+
{{ 'haloduck.ui.authenticate.Processing authentication' | transloco }}
|
|
591
|
+
</h2>
|
|
592
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
593
|
+
{{ 'haloduck.ui.authenticate.Processing social login...' | transloco }}
|
|
594
|
+
</p>
|
|
595
|
+
</div>
|
|
596
|
+
</div>
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
@if (error) {
|
|
600
|
+
<div class="space-y-6">
|
|
601
|
+
<div class="flex justify-center">
|
|
602
|
+
<div
|
|
603
|
+
class="w-16 h-16 mx-auto mb-4 flex items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20"
|
|
604
|
+
>
|
|
605
|
+
<svg
|
|
606
|
+
class="w-8 h-8 text-red-600 dark:text-red-400"
|
|
607
|
+
fill="currentColor"
|
|
608
|
+
viewBox="0 0 20 20"
|
|
609
|
+
>
|
|
610
|
+
<path
|
|
611
|
+
fill-rule="evenodd"
|
|
612
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
613
|
+
clip-rule="evenodd"
|
|
614
|
+
></path>
|
|
615
|
+
</svg>
|
|
616
|
+
</div>
|
|
617
|
+
</div>
|
|
618
|
+
<div class="space-y-4">
|
|
619
|
+
<h2
|
|
620
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
621
|
+
>
|
|
622
|
+
{{ 'haloduck.ui.authenticate.Authentication failed' | transloco }}
|
|
623
|
+
</h2>
|
|
624
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
625
|
+
{{ error && error.startsWith('haloduck.ui.') ? (error | transloco) : error }}
|
|
626
|
+
</p>
|
|
627
|
+
<button
|
|
628
|
+
(click)="redirectToLogin()"
|
|
629
|
+
class="w-full inline-flex items-center justify-center px-4 py-3 border border-transparent text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-200"
|
|
630
|
+
>
|
|
631
|
+
{{ 'haloduck.ui.authenticate.Back to login page' | transloco }}
|
|
632
|
+
</button>
|
|
633
|
+
</div>
|
|
634
|
+
</div>
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
@if (success) {
|
|
638
|
+
<div class="space-y-6">
|
|
639
|
+
<div class="flex justify-center">
|
|
640
|
+
<div
|
|
641
|
+
class="w-16 h-16 mx-auto mb-4 flex items-center justify-center rounded-full bg-green-100 dark:bg-green-900/20"
|
|
642
|
+
>
|
|
643
|
+
<svg
|
|
644
|
+
class="w-8 h-8 text-green-600 dark:text-green-400"
|
|
645
|
+
fill="currentColor"
|
|
646
|
+
viewBox="0 0 20 20"
|
|
647
|
+
>
|
|
648
|
+
<path
|
|
649
|
+
fill-rule="evenodd"
|
|
650
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
651
|
+
clip-rule="evenodd"
|
|
652
|
+
></path>
|
|
653
|
+
</svg>
|
|
654
|
+
</div>
|
|
655
|
+
</div>
|
|
656
|
+
<div class="space-y-4">
|
|
657
|
+
<h2
|
|
658
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
659
|
+
>
|
|
660
|
+
{{ 'haloduck.ui.authenticate.Authentication successful' | transloco }}
|
|
661
|
+
</h2>
|
|
662
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
663
|
+
{{ 'haloduck.ui.authenticate.Social login completed.' | transloco }}
|
|
664
|
+
</p>
|
|
665
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
666
|
+
{{ 'haloduck.ui.authenticate.Redirecting automatically...' | transloco }}
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
</div>
|
|
670
|
+
}
|
|
671
|
+
</div>
|
|
672
|
+
</div>
|
|
673
|
+
</div>
|
|
674
|
+
`, isInline: true, styles: [":host{display:block;width:100%;height:100vh}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
|
|
675
|
+
}
|
|
676
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: AuthenticateCallbackComponent, decorators: [{
|
|
677
|
+
type: Component,
|
|
678
|
+
args: [{ selector: 'haloduck-authenticate-callback', standalone: true, imports: [CommonModule, TranslocoModule], providers: [provideTranslocoScope('haloduck')], template: `
|
|
679
|
+
<div
|
|
680
|
+
class="flex flex-col items-center justify-center min-h-screen bg-light-background dark:bg-dark-background"
|
|
681
|
+
>
|
|
682
|
+
<div
|
|
683
|
+
class="w-full max-w-md mx-auto p-8 shadow-lg rounded-2xl bg-light-background dark:bg-dark-background"
|
|
684
|
+
>
|
|
685
|
+
<div class="text-center">
|
|
686
|
+
@if (isProcessing) {
|
|
687
|
+
<div class="space-y-6">
|
|
688
|
+
<div class="flex justify-center">
|
|
689
|
+
<div
|
|
690
|
+
class="animate-spin rounded-full h-16 w-16 border-4 border-gray-300 border-t-blue-600 dark:border-gray-600 dark:border-t-blue-400"
|
|
691
|
+
></div>
|
|
692
|
+
</div>
|
|
693
|
+
<div class="space-y-2">
|
|
694
|
+
<h2
|
|
695
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
696
|
+
>
|
|
697
|
+
{{ 'haloduck.ui.authenticate.Processing authentication' | transloco }}
|
|
698
|
+
</h2>
|
|
699
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
700
|
+
{{ 'haloduck.ui.authenticate.Processing social login...' | transloco }}
|
|
701
|
+
</p>
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
@if (error) {
|
|
707
|
+
<div class="space-y-6">
|
|
708
|
+
<div class="flex justify-center">
|
|
709
|
+
<div
|
|
710
|
+
class="w-16 h-16 mx-auto mb-4 flex items-center justify-center rounded-full bg-red-100 dark:bg-red-900/20"
|
|
711
|
+
>
|
|
712
|
+
<svg
|
|
713
|
+
class="w-8 h-8 text-red-600 dark:text-red-400"
|
|
714
|
+
fill="currentColor"
|
|
715
|
+
viewBox="0 0 20 20"
|
|
716
|
+
>
|
|
717
|
+
<path
|
|
718
|
+
fill-rule="evenodd"
|
|
719
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
720
|
+
clip-rule="evenodd"
|
|
721
|
+
></path>
|
|
722
|
+
</svg>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
<div class="space-y-4">
|
|
726
|
+
<h2
|
|
727
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
728
|
+
>
|
|
729
|
+
{{ 'haloduck.ui.authenticate.Authentication failed' | transloco }}
|
|
730
|
+
</h2>
|
|
731
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
732
|
+
{{ error && error.startsWith('haloduck.ui.') ? (error | transloco) : error }}
|
|
733
|
+
</p>
|
|
734
|
+
<button
|
|
735
|
+
(click)="redirectToLogin()"
|
|
736
|
+
class="w-full inline-flex items-center justify-center px-4 py-3 border border-transparent text-sm font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 dark:focus:ring-blue-400 transition-colors duration-200"
|
|
737
|
+
>
|
|
738
|
+
{{ 'haloduck.ui.authenticate.Back to login page' | transloco }}
|
|
739
|
+
</button>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
@if (success) {
|
|
745
|
+
<div class="space-y-6">
|
|
746
|
+
<div class="flex justify-center">
|
|
747
|
+
<div
|
|
748
|
+
class="w-16 h-16 mx-auto mb-4 flex items-center justify-center rounded-full bg-green-100 dark:bg-green-900/20"
|
|
749
|
+
>
|
|
750
|
+
<svg
|
|
751
|
+
class="w-8 h-8 text-green-600 dark:text-green-400"
|
|
752
|
+
fill="currentColor"
|
|
753
|
+
viewBox="0 0 20 20"
|
|
754
|
+
>
|
|
755
|
+
<path
|
|
756
|
+
fill-rule="evenodd"
|
|
757
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
758
|
+
clip-rule="evenodd"
|
|
759
|
+
></path>
|
|
760
|
+
</svg>
|
|
761
|
+
</div>
|
|
762
|
+
</div>
|
|
763
|
+
<div class="space-y-4">
|
|
764
|
+
<h2
|
|
765
|
+
class="text-xl font-semibold text-light-on-background dark:text-dark-on-background"
|
|
766
|
+
>
|
|
767
|
+
{{ 'haloduck.ui.authenticate.Authentication successful' | transloco }}
|
|
768
|
+
</h2>
|
|
769
|
+
<p class="text-gray-600 dark:text-gray-300">
|
|
770
|
+
{{ 'haloduck.ui.authenticate.Social login completed.' | transloco }}
|
|
771
|
+
</p>
|
|
772
|
+
<div class="text-sm text-gray-500 dark:text-gray-400">
|
|
773
|
+
{{ 'haloduck.ui.authenticate.Redirecting automatically...' | transloco }}
|
|
774
|
+
</div>
|
|
775
|
+
</div>
|
|
776
|
+
</div>
|
|
777
|
+
}
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
</div>
|
|
781
|
+
`, styles: [":host{display:block;width:100%;height:100vh}\n"] }]
|
|
782
|
+
}] });
|
|
347
783
|
|
|
348
784
|
class SelectDropdownComponent {
|
|
349
785
|
_filteredOptions = signal([], ...(ngDevMode ? [{ debugName: "_filteredOptions" }] : []));
|
|
@@ -2671,7 +3107,7 @@ class FileUploaderComponent {
|
|
|
2671
3107
|
multi: true,
|
|
2672
3108
|
},
|
|
2673
3109
|
provideTranslocoScope('haloduck'),
|
|
2674
|
-
], ngImport: i0, template: "<div\n class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @if (!isUploading) {\n <label\n for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\"\n >\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{\n 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco\n }}</span>\n <input\n id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\"\n />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center
|
|
3110
|
+
], ngImport: i0, template: "<div\n class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @if (!isUploading) {\n <label\n for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\"\n >\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{\n 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco\n }}</span>\n <input\n id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\"\n />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center sm:justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md gap-2\"\n >\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\"\n />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n <div class=\"flex items-center space-x-4\">\n <div class=\"flex-1\">\n <haloduck-tag-input\n placeholder=\"{{ 'haloduck.ui.tag.Please input tags.' | transloco }}\"\n [(value)]=\"file.tag\"\n (valueChange)=\"onFileTagChanged(i, $event)\"\n ></haloduck-tag-input>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{\n 'haloduck.ui.file.Uploaded' | transloco\n }}</span>\n } @else {\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{\n 'haloduck.ui.file.Uploading...' | transloco\n }}</span>\n }\n } @else {\n <button\n type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\"\n >\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: TranslocoModule }, { kind: "component", type: TagInputComponent, selector: "haloduck-tag-input", inputs: ["placeholder", "disabled", "allowDuplicates", "value"], outputs: ["valueChange"] }, { kind: "pipe", type: i2$1.TranslocoPipe, name: "transloco" }] });
|
|
2675
3111
|
}
|
|
2676
3112
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImport: i0, type: FileUploaderComponent, decorators: [{
|
|
2677
3113
|
type: Component,
|
|
@@ -2682,7 +3118,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.1.4", ngImpor
|
|
|
2682
3118
|
multi: true,
|
|
2683
3119
|
},
|
|
2684
3120
|
provideTranslocoScope('haloduck'),
|
|
2685
|
-
], template: "<div\n class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @if (!isUploading) {\n <label\n for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\"\n >\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{\n 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco\n }}</span>\n <input\n id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\"\n />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center
|
|
3121
|
+
], template: "<div\n class=\"p-4 border border-light-inactive dark:border-dark-inactive rounded-md\"\n [class.drag-over]=\"isDragOver\"\n (dragover)=\"onDragOver($event)\"\n (dragleave)=\"onDragLeave($event)\"\n (drop)=\"onDrop($event)\"\n>\n @if (!isUploading) {\n <label\n for=\"file-upload\"\n class=\"flex flex-col items-center justify-center w-full border-2 border-dashed border-light-inactive dark:border-dark-inactive rounded-md cursor-pointer hover:border-light-secondary dark:border-dark-secondary p-4\"\n >\n <span class=\"text-light-inactive dark:text-dark-inactive\">{{\n 'haloduck.ui.file.Drag and drop files here, or click to select files' | transloco\n }}</span>\n <input\n id=\"file-upload\"\n type=\"file\"\n class=\"hidden\"\n [attr.accept]=\"accept ? accept.join(',') : null\"\n [attr.multiple]=\"multiple ? '' : null\"\n (cancel)=\"$event.stopPropagation()\"\n (change)=\"onFileSelected($event)\"\n />\n </label>\n }\n <!-- Display file list -->\n @if (files.length > 0) {\n <ul class=\"mt-4 space-y-2\">\n @for (file of files; track file.name; let i = $index) {\n <li\n class=\"flex flex-col sm:flex-row items-stretch sm:items-center sm:justify-between p-2 border border-light-inactive dark:border-dark-inactive rounded-md gap-2\"\n >\n <!-- Check if the file is an image -->\n <div class=\"flex items-center space-x-4\">\n @if (file.previewUrl) {\n <img\n [src]=\"file.previewUrl\"\n alt=\"{{ file.name }}\"\n class=\"w-12 h-12 object-cover rounded-md\"\n />\n }\n <span class=\"text-sm text-light-inactive dark:text-dark-inactive\">{{ file.name }}</span>\n </div>\n <div class=\"flex items-center space-x-4\">\n <div class=\"flex-1\">\n <haloduck-tag-input\n placeholder=\"{{ 'haloduck.ui.tag.Please input tags.' | transloco }}\"\n [(value)]=\"file.tag\"\n (valueChange)=\"onFileTagChanged(i, $event)\"\n ></haloduck-tag-input>\n </div>\n @if (isUploading) {\n @if (file.isUploaded) {\n <span class=\"text-sm text-light-secondary dark:text-dark-secondary\">{{\n 'haloduck.ui.file.Uploaded' | transloco\n }}</span>\n } @else {\n <span class=\"text-sm text-light-primary dark:text-dark-primary\">{{\n 'haloduck.ui.file.Uploading...' | transloco\n }}</span>\n }\n } @else {\n <button\n type=\"button\"\n class=\"text-light-danger dark:text-dark-danger hover:brightness-125\"\n (click)=\"removeFile(i)\"\n >\n {{ 'haloduck.ui.file.Remove' | transloco }}\n </button>\n }\n </div>\n </li>\n }\n </ul>\n }\n</div>\n" }]
|
|
2686
3122
|
}], propDecorators: { disabled: [{
|
|
2687
3123
|
type: Input
|
|
2688
3124
|
}], urlPrefix: [{
|
|
@@ -4403,5 +4839,5 @@ const provideHaloduckTransloco = () => provideTranslocoScope({
|
|
|
4403
4839
|
* Generated bundle index. Do not edit.
|
|
4404
4840
|
*/
|
|
4405
4841
|
|
|
4406
|
-
export { AuthenticateComponent, AutoLoadDirective, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, GroupedDirective, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, TableSettingComponent, TableSettingService, TabsComponent, TagInputComponent, TagViewerComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
|
|
4842
|
+
export { AuthenticateCallbackComponent, AuthenticateComponent, AutoLoadDirective, BreadcrumbComponent, ButtonComponent, CalendarComponent, ConfirmDialogService, CopyButtonComponent, DatePickerComponent, DateRangeComponent, DialogService, DrawCanvasComponent, ERROR_NOT_ACCEPTABLE_FILE_TYPE, ERROR_OVER_COUNT, ERROR_OVER_SIZE, ERROR_UPLOAD, FileUploaderComponent, FlipComponent, GroupedDirective, ImageUploaderComponent, ImageViewerComponent, InputComponent, LanguageSelectorComponent, MapToAddressComponent, NotificationComponent, NotificationService, PictureNameComponent, SelectComponent, SelectDropdownComponent, SideMenuComponent, SideMenuItemComponent, StlViewerComponent, TableComponent, TableSettingComponent, TableSettingService, TabsComponent, TagInputComponent, TagViewerComponent, ToggleComponent, dateToString, provideHaloduckTransloco };
|
|
4407
4843
|
//# sourceMappingURL=haloduck-ui.mjs.map
|