Files
2026-07-01 10:30:32 +09:00

604 lines
23 KiB
JSON

{
"version": "1.0.0",
"layout_name": "auth/reset_password",
"permissions": [],
"extends": "_user_base",
"meta": {
"title": "$t:auth.reset_password.title",
"guest_only": true
},
"data_sources": [
{
"id": "tokenValidation",
"label_key": "$t:editor.data_source.tokenValidation",
"type": "api",
"endpoint": "/api/auth/validate-reset-token",
"method": "POST",
"auto_fetch": true,
"auth_required": false,
"params": {
"token": "{{query.token ?? ''}}",
"email": "{{query.email ?? ''}}"
},
"loading_strategy": "blocking",
"initLocal": {
"tokenValid": "{{data.valid ?? false}}"
},
"errorHandling": {
"422": {
"handler": "setState",
"params": {
"target": "local",
"tokenError": true
}
},
"default": {
"handler": "setState",
"params": {
"target": "local",
"tokenError": true
}
}
}
}
],
"slots": {
"content": [
{
"comment": "본인 계정 모달 - 로그인된 사용자 이메일과 토큰 이메일이 일치하는 경우",
"type": "composite",
"name": "Modal",
"props": {
"isOpen": "{{!!_global?.currentUser?.uuid && query.email === _global?.currentUser?.email}}",
"title": "$t:auth.logged_in_redirect.title",
"width": "400px",
"closeOnBackdropClick": false
},
"actions": [
{
"event": "onClose",
"type": "click",
"handler": "navigate",
"params": { "path": "/" }
}
],
"children": [
{
"type": "basic",
"name": "P",
"props": {
"className": "text-gray-600 dark:text-gray-400"
},
"text": "$t:auth.logged_in_redirect.message"
},
{
"comment": "Footer 버튼 영역",
"type": "basic",
"name": "Div",
"props": {
"className": "flex justify-end gap-3 mt-6"
},
"children": [
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer"
},
"text": "$t:auth.logged_in_redirect.go_home",
"actions": [
{
"type": "click",
"handler": "navigate",
"params": { "path": "/" }
}
]
},
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors cursor-pointer"
},
"text": "$t:auth.logged_in_redirect.change_password",
"actions": [
{
"type": "click",
"handler": "navigate",
"params": { "path": "/mypage/change-password" }
}
]
}
]
}
]
},
{
"comment": "타인 계정 모달 - 로그인된 사용자 이메일과 토큰 이메일이 다른 경우",
"type": "composite",
"name": "Modal",
"props": {
"isOpen": "{{!!_global?.currentUser?.uuid && query.email !== _global?.currentUser?.email}}",
"title": "$t:auth.different_account.title",
"width": "400px",
"closeOnBackdropClick": false
},
"actions": [
{
"event": "onClose",
"type": "click",
"handler": "conditions",
"params": {
"conditions": [
{
"if": "{{!_local.isLoggingOut}}",
"then": {
"handler": "navigate",
"params": { "path": "/" }
}
}
]
}
}
],
"children": [
{
"type": "basic",
"name": "P",
"props": {
"className": "text-gray-600 dark:text-gray-400"
},
"text": "$t:auth.different_account.message"
},
{
"comment": "Footer 버튼 영역",
"type": "basic",
"name": "Div",
"props": {
"className": "flex justify-end gap-3 mt-6"
},
"children": [
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",
"disabled": "{{_local.isLoggingOut}}"
},
"text": "$t:auth.different_account.go_home",
"actions": [
{
"type": "click",
"handler": "navigate",
"params": { "path": "/" }
}
]
},
{
"type": "basic",
"name": "Button",
"props": {
"type": "button",
"className": "flex items-center justify-center gap-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed",
"disabled": "{{_local.isLoggingOut}}"
},
"children": [
{
"type": "basic",
"name": "Div",
"if": "{{_local.isLoggingOut}}",
"props": {
"className": "w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin"
}
},
{
"type": "basic",
"name": "Span",
"text": "{{_local.isLoggingOut ? '$t:auth.different_account.logging_out' : '$t:auth.different_account.logout'}}"
}
],
"actions": [
{
"type": "click",
"handler": "sequence",
"actions": [
{
"handler": "setState",
"params": {
"target": "local",
"isLoggingOut": true
}
},
{
"handler": "logout",
"onSuccess": {
"handler": "refresh"
},
"onError": {
"handler": "sequence",
"actions": [
{
"handler": "setState",
"params": {
"target": "local",
"isLoggingOut": false
}
},
{
"handler": "toast",
"params": {
"type": "error",
"message": "$t:auth.different_account.logout_failed"
}
}
]
}
}
]
}
]
}
]
}
]
},
{
"type": "basic",
"name": "Div",
"blur_until_loaded": "{{_global?.currentUser?.uuid}}",
"props": {
"className": "min-h-[60vh] flex items-center justify-center py-12 px-4"
},
"children": [
{
"comment": "비밀번호 재설정 폼",
"type": "basic",
"name": "Div",
"props": {
"className": "w-full max-w-md"
},
"children": [
{
"type": "basic",
"name": "Div",
"props": {
"className": "bg-white dark:bg-gray-800 rounded-lg shadow-md p-8"
},
"responsive": {
"portable": {
"props": {
"className": "bg-white dark:bg-gray-800 rounded-lg shadow-md p-6"
}
}
},
"children": [
{
"comment": "헤더",
"type": "basic",
"name": "Div",
"props": {
"className": "text-center mb-8"
},
"children": [
{
"type": "basic",
"name": "H1",
"props": {
"className": "text-2xl font-bold text-gray-900 dark:text-white mb-2"
},
"text": "$t:auth.reset_password.title"
},
{
"type": "basic",
"name": "P",
"props": {
"className": "text-gray-600 dark:text-gray-400"
},
"text": "$t:auth.reset_password.subtitle"
}
]
},
{
"comment": "토큰 에러 메시지",
"type": "basic",
"name": "Div",
"if": "{{_local?.tokenError}}",
"props": {
"className": "text-center"
},
"children": [
{
"type": "basic",
"name": "Div",
"props": {
"className": "w-16 h-16 mx-auto mb-4 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center"
},
"children": [
{
"type": "basic",
"name": "Icon",
"props": {
"name": "xmark",
"size": "xl",
"color": "text-red-600 dark:text-red-400"
}
}
]
},
{
"type": "basic",
"name": "P",
"props": {
"className": "text-red-700 dark:text-red-300 mb-6"
},
"text": "$t:auth.reset_password.token_invalid"
},
{
"type": "basic",
"name": "Div",
"props": {
"className": "flex flex-col sm:flex-row gap-3 justify-center"
},
"children": [
{
"type": "basic",
"name": "Button",
"props": {
"className": "px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors cursor-pointer whitespace-nowrap"
},
"actions": [
{
"type": "click",
"handler": "navigate",
"params": {
"path": "/login"
}
}
],
"text": "$t:auth.reset_password.go_to_login"
},
{
"type": "basic",
"name": "Button",
"props": {
"className": "px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors cursor-pointer whitespace-nowrap"
},
"actions": [
{
"type": "click",
"handler": "navigate",
"params": {
"path": "/"
}
}
],
"text": "$t:auth.reset_password.go_to_home"
}
]
}
]
},
{
"comment": "성공 메시지",
"type": "basic",
"name": "Div",
"props": {
"className": "text-center"
},
"if": "{{_local?.success && !_local?.tokenError}}",
"children": [
{
"type": "basic",
"name": "Div",
"props": {
"className": "w-16 h-16 mx-auto mb-4 bg-green-100 dark:bg-green-900/30 rounded-full flex items-center justify-center"
},
"children": [
{
"type": "basic",
"name": "Icon",
"props": {
"name": "check",
"size": "xl",
"color": "text-green-600 dark:text-green-400"
}
}
]
},
{
"type": "basic",
"name": "P",
"props": {
"className": "text-green-700 dark:text-green-300 mb-4"
},
"text": "$t:auth.reset_password.success"
},
{
"type": "basic",
"name": "Button",
"props": {
"className": "inline-block px-6 py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors cursor-pointer"
},
"actions": [
{
"type": "click",
"handler": "navigate",
"params": {
"path": "/login"
}
}
],
"text": "$t:auth.login"
}
]
},
{
"comment": "폼",
"type": "basic",
"name": "Form",
"dataKey": "form",
"props": {
"id": "form",
"className": "space-y-6"
},
"if": "{{!_local?.success && !_local?.tokenError}}",
"actions": [
{
"type": "submit",
"handler": "sequence",
"actions": [
{
"handler": "setState",
"params": {
"target": "local",
"isSubmitting": true
}
},
{
"handler": "apiCall",
"target": "/api/auth/reset-password",
"params": {
"method": "POST",
"body": {
"token": "{{query.token ?? ''}}",
"email": "{{query.email ?? ''}}",
"password": "{{form?.password}}",
"password_confirmation": "{{form?.password_confirmation}}"
}
},
"onSuccess": {
"handler": "setState",
"params": {
"target": "local",
"isSubmitting": false,
"success": true
}
},
"onError": [
{
"handler": "setState",
"params": {
"target": "local",
"isSubmitting": false
}
},
{
"handler": "toast",
"params": {
"type": "error",
"message": "{{error.message}}"
}
}
]
}
]
}
],
"children": [
{
"comment": "새 비밀번호",
"type": "basic",
"name": "Div",
"children": [
{
"type": "basic",
"name": "Label",
"props": {
"className": "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
},
"text": "$t:auth.reset_password.password"
},
{
"type": "basic",
"name": "PasswordInput",
"props": {
"name": "password",
"placeholder": "$t:auth.reset_password.password_placeholder|count=8",
"className": "w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed",
"required": true,
"showToggle": true,
"showValidation": true,
"enableRules": [
"minLength:8"
],
"disabled": "{{_local?.isSubmitting ?? false}}"
}
}
]
},
{
"comment": "비밀번호 확인",
"type": "basic",
"name": "Div",
"children": [
{
"type": "basic",
"name": "Label",
"props": {
"className": "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"
},
"text": "$t:auth.reset_password.password_confirm"
},
{
"type": "basic",
"name": "PasswordInput",
"props": {
"name": "password_confirmation",
"placeholder": "$t:auth.reset_password.password_confirm_placeholder",
"className": "w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-primary-500 focus:border-transparent disabled:opacity-50 disabled:cursor-not-allowed",
"required": true,
"showToggle": true,
"isConfirmField": true,
"confirmTarget": "{{_local?.form?.password ?? ''}}",
"disabled": "{{_local?.isSubmitting ?? false}}"
}
}
]
},
{
"comment": "제출 버튼",
"type": "basic",
"name": "Button",
"props": {
"type": "submit",
"className": "w-full py-3 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2",
"disabled": "{{_local?.isSubmitting ?? false}}"
},
"children": [
{
"comment": "스피너",
"type": "basic",
"name": "Div",
"if": "{{_local?.isSubmitting}}",
"props": {
"className": "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin"
}
},
{
"type": "basic",
"name": "Span",
"if": "{{!_local?.isSubmitting}}",
"text": "$t:auth.reset_password.submit"
},
{
"type": "basic",
"name": "Span",
"if": "{{_local?.isSubmitting}}",
"text": "$t:auth.reset_password.processing"
}
]
}
]
}
]
}
]
}
]
}
]
}
}