fix: 로그인 유도 모달 개편과 공유 메뉴 방향·다운로드 파일명·정렬 수정
This commit is contained in:
parent
1c01dab748
commit
18b5271110
@ -26,22 +26,24 @@ const LoginPromptModal: React.FC<LoginPromptModalProps> = ({ onClose }) => {
|
||||
style={{
|
||||
position: 'fixed',
|
||||
inset: 0,
|
||||
background: 'rgba(255, 255, 255, 0.25)',
|
||||
background: 'rgba(0, 0, 0, 0.7)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: 9999,
|
||||
padding: '16px',
|
||||
}}
|
||||
{...overlayCloseHandlers}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
background: '#01282A',
|
||||
border: '1px solid rgba(155, 202, 204, 0.2)',
|
||||
borderRadius: '16px',
|
||||
padding: '40px 32px',
|
||||
width: '360px',
|
||||
maxWidth: '90vw',
|
||||
border: '1px solid rgba(166, 255, 234, 0.15)',
|
||||
borderRadius: '20px',
|
||||
boxShadow: '0 24px 60px rgba(0, 0, 0, 0.5)',
|
||||
padding: '44px 32px 36px',
|
||||
width: '380px',
|
||||
maxWidth: '100%',
|
||||
textAlign: 'center',
|
||||
position: 'relative',
|
||||
}}
|
||||
@ -66,27 +68,49 @@ const LoginPromptModal: React.FC<LoginPromptModalProps> = ({ onClose }) => {
|
||||
<line x1="16" y1="4" x2="4" y2="16"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h2 style={{ color: '#FFFFFF', fontSize: '20px', fontWeight: 600, marginBottom: '12px' }}>
|
||||
|
||||
<div
|
||||
style={{
|
||||
width: '56px',
|
||||
height: '56px',
|
||||
margin: '0 auto 20px',
|
||||
borderRadius: '50%',
|
||||
background: 'rgba(166, 255, 234, 0.1)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#A6FFEA" strokeWidth="2">
|
||||
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
|
||||
<circle cx="12" cy="7" r="4" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<h2 style={{ color: '#FFFFFF', fontSize: '22px', fontWeight: 700, marginBottom: '10px', lineHeight: 1.4 }}>
|
||||
{t('loginPrompt.title')}
|
||||
</h2>
|
||||
<p style={{ color: '#9BCACC', fontSize: '14px', lineHeight: 1.5, marginBottom: '28px', whiteSpace: 'pre-line' }}>
|
||||
{t('loginPrompt.description')}
|
||||
</p>
|
||||
|
||||
<button
|
||||
onClick={handleLogin}
|
||||
style={{
|
||||
width: '50%',
|
||||
margin: '0 auto',
|
||||
padding: '14px',
|
||||
width: '100%',
|
||||
padding: '15px',
|
||||
background: '#FEE500',
|
||||
color: '#3C1E1E',
|
||||
border: 'none',
|
||||
borderRadius: '8px',
|
||||
fontSize: '15px',
|
||||
fontWeight: 600,
|
||||
borderRadius: '10px',
|
||||
fontSize: '16px',
|
||||
fontWeight: 700,
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px',
|
||||
boxShadow: '0 4px 14px rgba(254, 229, 0, 0.25)',
|
||||
}}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@ -30,6 +30,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [shareMenuOpen, setShareMenuOpen] = useState(false);
|
||||
const [shareMenuUpward, setShareMenuUpward] = useState(false);
|
||||
const [isLandscape, setIsLandscape] = useState(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||
|
||||
@ -158,6 +159,23 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [shareMenuOpen]);
|
||||
|
||||
// 모달 내부는 스크롤 영역(.video-detail-body)이 곧 화면 경계라, 버튼이 그 하단에
|
||||
// 가까우면 아래로 펼치는 메뉴가 잘려서 안 보인다. 열기 직전 남은 공간을 재서 뒤집는다.
|
||||
const handleToggleShareMenu = () => {
|
||||
if (!shareMenuOpen) {
|
||||
const btnRect = shareMenuRef.current?.getBoundingClientRect();
|
||||
if (btnRect) {
|
||||
const scrollBox = shareMenuRef.current?.closest('.video-detail-body');
|
||||
const boundaryBottom = scrollBox
|
||||
? scrollBox.getBoundingClientRect().bottom
|
||||
: window.innerHeight;
|
||||
const ESTIMATED_MENU_HEIGHT = 220;
|
||||
setShareMenuUpward(btnRect.bottom + ESTIMATED_MENU_HEIGHT > boundaryBottom);
|
||||
}
|
||||
}
|
||||
setShareMenuOpen(v => !v);
|
||||
};
|
||||
|
||||
const likeDebounceRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const handleLike = () => {
|
||||
@ -292,7 +310,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
|
||||
<div style={{ position: 'relative' }} ref={shareMenuRef}>
|
||||
<button
|
||||
className="video-detail-copy-btn"
|
||||
onClick={() => setShareMenuOpen(v => !v)}
|
||||
onClick={handleToggleShareMenu}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
|
||||
@ -301,7 +319,7 @@ const VideoDetailContent: React.FC<VideoDetailContentProps> = ({ videoId, isModa
|
||||
{copied ? t('videoDetail.copied') : t('videoDetail.share')}
|
||||
</button>
|
||||
{shareMenuOpen && (
|
||||
<div className="video-detail-share-menu">
|
||||
<div className={`video-detail-share-menu ${shareMenuUpward ? 'upward' : ''}`}>
|
||||
{/* 카카오톡 */}
|
||||
<button className="video-detail-share-item" onClick={handleKakaoShare}>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
@ -559,7 +559,8 @@
|
||||
},
|
||||
"loginPrompt": {
|
||||
"title": "Login Required",
|
||||
"loginBtn": "Login"
|
||||
"description": "Sign in with Kakao in a few seconds\nto keep going.",
|
||||
"loginBtn": "Continue with Kakao"
|
||||
},
|
||||
"videoDetail": {
|
||||
"dateFormat": "{{month}}/{{day}}/{{year}}",
|
||||
|
||||
@ -607,7 +607,8 @@
|
||||
},
|
||||
"loginPrompt": {
|
||||
"title": "로그인이 필요합니다.",
|
||||
"loginBtn": "로그인"
|
||||
"description": "카카오 계정으로 간편하게 로그인하고\n이어서 진행해보세요.",
|
||||
"loginBtn": "카카오 로그인"
|
||||
},
|
||||
"videoDetail": {
|
||||
"dateFormat": "{{year}}년 {{month}}월 {{day}}일",
|
||||
|
||||
@ -434,7 +434,7 @@ const ContentCalendarContent: React.FC<ContentCalendarContentProps> = ({ onNavig
|
||||
|
||||
// ── 탭 바 ────────────────────────────────────────────────────
|
||||
const renderTabs = () => (
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center', justifyContent: 'center', width: '100%' }}>
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab}
|
||||
|
||||
@ -45,6 +45,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
|
||||
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [shareMenuOpen, setShareMenuOpen] = useState(false);
|
||||
const [shareMenuUpward, setShareMenuUpward] = useState(false);
|
||||
const [isLandscape, setIsLandscape] = useState(false);
|
||||
const [showLoginModal, setShowLoginModal] = useState(false);
|
||||
|
||||
@ -184,6 +185,23 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, [shareMenuOpen]);
|
||||
|
||||
// 모달 내부는 스크롤 영역(.video-detail-body)이 곧 화면 경계라, 버튼이 그 하단에
|
||||
// 가까우면 아래로 펼치는 메뉴가 잘려서 안 보인다. 열기 직전 남은 공간을 재서 뒤집는다.
|
||||
const handleToggleShareMenu = () => {
|
||||
if (!shareMenuOpen) {
|
||||
const btnRect = shareMenuRef.current?.getBoundingClientRect();
|
||||
if (btnRect) {
|
||||
const scrollBox = shareMenuRef.current?.closest('.video-detail-body');
|
||||
const boundaryBottom = scrollBox
|
||||
? scrollBox.getBoundingClientRect().bottom
|
||||
: window.innerHeight;
|
||||
const ESTIMATED_MENU_HEIGHT = 220;
|
||||
setShareMenuUpward(btnRect.bottom + ESTIMATED_MENU_HEIGHT > boundaryBottom);
|
||||
}
|
||||
}
|
||||
setShareMenuOpen(v => !v);
|
||||
};
|
||||
|
||||
const likeDebounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const handleLike = () => {
|
||||
@ -334,7 +352,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
|
||||
<div style={{ position: 'relative' }} ref={shareMenuRef}>
|
||||
<button
|
||||
className="video-detail-copy-btn"
|
||||
onClick={() => setShareMenuOpen((v) => !v)}
|
||||
onClick={handleToggleShareMenu}
|
||||
>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||
<circle cx="18" cy="5" r="3" /><circle cx="6" cy="12" r="3" /><circle cx="18" cy="19" r="3" />
|
||||
@ -343,7 +361,7 @@ const SsulDetailContent: React.FC<SsulDetailContentProps> = ({
|
||||
{copied ? t('videoDetail.copied') : t('videoDetail.share')}
|
||||
</button>
|
||||
{shareMenuOpen && (
|
||||
<div className="video-detail-share-menu">
|
||||
<div className={`video-detail-share-menu ${shareMenuUpward ? 'upward' : ''}`}>
|
||||
<button className="video-detail-share-item" onClick={handleKakaoShare}>
|
||||
<svg width="18" height="18" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="20" height="20" rx="4" fill="#FEE500" />
|
||||
|
||||
@ -47,8 +47,10 @@ const SsulResultContent: React.FC<SsulResultContentProps> = ({ taskId, scenario
|
||||
};
|
||||
}, [taskId]);
|
||||
|
||||
// 파일명은 프론트가 정한다(백엔드는 title 을 갖지 않는다)
|
||||
const fileName = `ssulbox-${scenario}-${taskId}.mp4`;
|
||||
// 파일명은 ADO2 완료 화면과 같은 방식이다 — 업장명 그대로 + .mp4.
|
||||
// store_name 은 완료(done) 시점에 서버가 반드시 채워 보내지만, 조회 실패로
|
||||
// task 가 없을 수 있어 ADO2 와 동일한 폴백('콘텐츠')을 둔다.
|
||||
const fileName = `${task?.store_name || '콘텐츠'}.mp4`;
|
||||
|
||||
/**
|
||||
* 영상 다운로드.
|
||||
|
||||
@ -747,7 +747,7 @@
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #FFFFFF;
|
||||
margin: 0;
|
||||
margin-left: 24px;
|
||||
}
|
||||
|
||||
.video-detail-back-btn {
|
||||
@ -769,7 +769,7 @@
|
||||
|
||||
.video-detail-content {
|
||||
display: flex;
|
||||
gap: 32px;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
@ -867,6 +867,12 @@
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
/* 버튼 아래 공간이 부족하면(모달 스크롤 영역 하단 근처) 위로 펼친다 */
|
||||
.video-detail-share-menu.upward {
|
||||
top: auto;
|
||||
bottom: calc(100% + 6px);
|
||||
}
|
||||
|
||||
.video-detail-share-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@ -369,8 +369,13 @@
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
/* 부모(.wizard-page-container)가 height:100% flex column 이다.
|
||||
`0 auto` 면 수평만 중앙이라 스텝퍼 바로 아래에 붙고 세로 여백이 전부
|
||||
/* ssulbox.css 의 베이스 `.ssul-scope { height:100% }` (원본 전체화면 셸 규칙)가
|
||||
여기까지 상속되어 이 박스 자체가 부모 높이를 꽉 채워버린다. 그러면 아래
|
||||
margin:auto 가 나눠 가질 여유 공간이 0 이 되어 컨텐츠가 위로 쏠린다.
|
||||
height:auto 로 되돌려 컨텐츠 크기만큼만 차지하게 해야 margin:auto 가 뜻대로
|
||||
작동한다(부모 .wizard-page-container 가 height:100% flex column). */
|
||||
height: auto;
|
||||
/* `0 auto` 면 수평만 중앙이라 스텝퍼 바로 아래에 붙고 세로 여백이 전부
|
||||
아래로 몰린다. `auto` 는 상하 여백도 균등 분배해 스텝퍼~바닥 사이
|
||||
중앙에 놓인다. */
|
||||
margin: auto;
|
||||
|
||||
@ -1461,6 +1461,11 @@ export interface SsulTaskStatus {
|
||||
step: number;
|
||||
error: string | null;
|
||||
video_url: string | null;
|
||||
/**
|
||||
* 대상 업장명. `status === 'done'` 이면 서버가 채워 보낸다.
|
||||
* 생성 중에는 비어 있을 수 있다 — 행은 요청 즉시 만들어지고 업장명은 그 뒤에 확정된다.
|
||||
*/
|
||||
store_name: string;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user