@designbasekorea/figma-ui 0.7.0 → 0.7.1

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/dist/index.esm.js CHANGED
@@ -2,7 +2,7 @@ import React, { createContext, useMemo, useContext, useRef, useState, useLayoutE
2
2
  import { Button, Dropdown, Badge, Toggle, Modal, ModalBody, Logo, Toast, Input, Spinner, SegmentControl, Progressbar, RandomGradient } from '@designbasekorea/ui-wc/react';
3
3
  export * from '@designbasekorea/ui-wc/react';
4
4
  export { Accordion, ActivityHeatmap, AdBanner, AiChat, AiChatList, Alert, AnimationBackground, AnimationText, AppBadge, AudioPlayer, Avatar, Backdrop, Badge, Banner, BottomNavigation, BottomSheet, Breadcrumbs, Button, Calendar, Card, Carousel, ChatComposer, ChatList, ChatMessage, ChatOrb, ChatRoom, ChatSuggestions, ChatTyping, ChatWelcome, ChatWidget, Checkbox, Chip, CodeBlock, ColorPicker, ColorSwatch, Confirm, Container, ContextMenu, CookieBanner, Countdown, CursorFollower, DatePicker, Divider, DownloadProgress, Drawer, Dropdown, Dropzone, EmailVerify, EmptyState, FileUploader, FloatingActionButton, Footer, Form, Gantt, Gradient, Grid, GridBackground, GridItem, Image, ImageList, ImagePlaceholder, Indicator, Input, Kanban, Label, Lightbox, List, Logo, MarkdownEditor, Marquee, Masonry, MenuItem, Modal, ModalBody, ModalFooter, ModalHeader, Navbar, OnboardingModal, OtpInput, PageHeader, Pagination, PhoneVerify, Popover, Progress, ProgressStep, Progressbar, Radio, RandomGradient, RangeSlider, Rating, Reorder, ResizablePanels, ScrollArea, SearchBar, Section, SectionAppDownload, SectionContact, SectionCta, SectionFaq, SectionFeature, SectionFeatureGrid, SectionHero, SectionLogoCloud, SectionPricing, SectionStats, SectionTestimonials, SegmentControl, Select, Share, Sidebar, Signature, Skeleton, Spinner, SplitView, Stack, Stat, Stepper, Table, Tabs, Testimonial, Textarea, TimePicker, Timeline, Toast, Toggle, Toolbar, Tooltip, TopBanner, Tutorial, TwoFactor, VideoPlayer, YouTubePlayer, useContextMenu } from '@designbasekorea/ui-wc/react';
5
- import { ChevronDownIcon, AppWindowIcon, YoutubeIcon, InstagramIcon, FigmaIcon, MailIcon, ExternalLinkIcon, TranslateIcon, MoreHorizontalIcon, CloseIcon, CircleCheckFilledIcon, ResizableIcon, GripVerticalIcon, ChevronRightIcon } from '@designbasekorea/icons';
5
+ import { ChevronDownIcon, AppWindowIcon, YoutubeIcon, InstagramIcon, FigmaIcon, MailIcon, ExternalLinkIcon, TranslateIcon, CloseIcon, CircleCheckFilledIcon, ResizableIcon, GripVerticalIcon, ChevronRightIcon } from '@designbasekorea/icons';
6
6
  import { DEFAULT_LICENSE_MESSAGE_TYPES } from '@designbasekorea/figma-license-core';
7
7
  export { DEFAULT_LICENSE_MESSAGE_TYPES } from '@designbasekorea/figma-license-core';
8
8
 
@@ -53,12 +53,16 @@ const FIGMA_UI_TRANSLATIONS = {
53
53
  'form.submit': '제출',
54
54
  'form.verifying': '확인 중...',
55
55
  'license.youtube_membership': 'YouTube 멤버십',
56
+ 'license.current_plan': '현재 플랜',
57
+ 'license.pro_plan': '프로 플랜',
58
+ 'license.membership': '멤버십',
56
59
  'license.pro_account': '프로 계정',
57
60
  'license.upgrade_to_pro': '프로로 업그레이드',
58
61
  'license.all_features_available': '모든 기능을 사용할 수 있습니다.',
59
62
  'license.youtube_membership_description': 'YouTube 멤버십에 가입하면 Designbase 플러그인을 무제한으로 사용할 수 있습니다.',
60
63
  'license.purchase_for_unlimited': '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.',
61
64
  'license.usage_unlimited': '사용 횟수 제한 없음',
65
+ 'license.active_until_expiry': '만료일까지 모든 기능을 제한 없이 사용할 수 있습니다.',
62
66
  'license.usage_daily': '오늘 남은 횟수 {{remaining}}/{{limit}}',
63
67
  'license.usage_weekly': '이번 주 남은 횟수 {{remaining}}/{{limit}}',
64
68
  'license.usage_monthly': '이번 달 남은 횟수 {{remaining}}/{{limit}}',
@@ -68,6 +72,7 @@ const FIGMA_UI_TRANSLATIONS = {
68
72
  'license.spots_remaining': '자리 남음',
69
73
  'license.deactivating': '비활성화 중...',
70
74
  'license.deactivate': '라이선스 비활성화',
75
+ 'license.deactivate_hint': '이 기기에서 라이선스 연결을 해제합니다.',
71
76
  'license.designbase_account': '디자인베이스 계정',
72
77
  'license.designbase_account_hint': 'WordPress 계정으로 로그인하거나 멤버십을 구매하면 플러그인이 자동으로 활성화됩니다.',
73
78
  'license.authenticating': '인증 중...',
@@ -186,12 +191,16 @@ const FIGMA_UI_TRANSLATIONS = {
186
191
  'form.submit': 'Activate',
187
192
  'form.verifying': 'Verifying...',
188
193
  'license.youtube_membership': 'YouTube Membership',
194
+ 'license.current_plan': 'Current plan',
195
+ 'license.pro_plan': 'Pro plan',
196
+ 'license.membership': 'Membership',
189
197
  'license.pro_account': 'Pro account',
190
198
  'license.upgrade_to_pro': 'Upgrade to Pro',
191
199
  'license.all_features_available': 'All features are available.',
192
200
  'license.youtube_membership_description': 'Join YouTube Membership to use Designbase plugins without limits.',
193
201
  'license.purchase_for_unlimited': 'Get unlimited access with a Designbase membership or license.',
194
202
  'license.usage_unlimited': 'Unlimited usage',
203
+ 'license.active_until_expiry': 'All features remain available without limits until expiration.',
195
204
  'license.usage_daily': '{{remaining}}/{{limit}} remaining today',
196
205
  'license.usage_weekly': '{{remaining}}/{{limit}} remaining this week',
197
206
  'license.usage_monthly': '{{remaining}}/{{limit}} remaining this month',
@@ -201,6 +210,7 @@ const FIGMA_UI_TRANSLATIONS = {
201
210
  'license.spots_remaining': 'spots remaining',
202
211
  'license.deactivating': 'Deactivating...',
203
212
  'license.deactivate': 'Deactivate license',
213
+ 'license.deactivate_hint': 'Disconnects this license from the current device.',
204
214
  'license.designbase_account': 'Designbase account',
205
215
  'license.designbase_account_hint': 'Sign in with your WordPress account or purchase a membership to activate the plugin automatically.',
206
216
  'license.authenticating': 'Authenticating...',
@@ -319,12 +329,16 @@ const FIGMA_UI_TRANSLATIONS = {
319
329
  'form.submit': '有効化',
320
330
  'form.verifying': '確認中...',
321
331
  'license.youtube_membership': 'YouTubeメンバーシップ',
332
+ 'license.current_plan': '現在のプラン',
333
+ 'license.pro_plan': 'Proプラン',
334
+ 'license.membership': 'メンバーシップ',
322
335
  'license.pro_account': 'Proアカウント',
323
336
  'license.upgrade_to_pro': 'Proにアップグレード',
324
337
  'license.all_features_available': 'すべての機能を使用できます。',
325
338
  'license.youtube_membership_description': 'YouTubeメンバーシップに参加すると、Designbaseプラグインを無制限で使用できます。',
326
339
  'license.purchase_for_unlimited': 'Designbaseのメンバーシップまたはライセンスで無制限に利用できます。',
327
340
  'license.usage_unlimited': '使用回数の制限なし',
341
+ 'license.active_until_expiry': '有効期限まで、すべての機能を制限なく使用できます。',
328
342
  'license.usage_daily': '今日の残り {{remaining}}/{{limit}}回',
329
343
  'license.usage_weekly': '今週の残り {{remaining}}/{{limit}}回',
330
344
  'license.usage_monthly': '今月の残り {{remaining}}/{{limit}}回',
@@ -334,6 +348,7 @@ const FIGMA_UI_TRANSLATIONS = {
334
348
  'license.spots_remaining': '枠残り',
335
349
  'license.deactivating': '無効化中...',
336
350
  'license.deactivate': 'ライセンスを無効化',
351
+ 'license.deactivate_hint': 'このデバイスからライセンスの接続を解除します。',
337
352
  'license.designbase_account': 'Designbaseアカウント',
338
353
  'license.designbase_account_hint': 'WordPressアカウントでサインインするか、メンバーシップを購入するとプラグインが自動的に有効化されます。',
339
354
  'license.authenticating': '認証中...',
@@ -982,7 +997,7 @@ const fallbackText = (t, key, fallback) => {
982
997
  const translated = t?.(key);
983
998
  return translated && translated !== key ? translated : fallback;
984
999
  };
985
- const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, maxDailyUsage = 20, showUsage = true, showPlan = true, isLoading = false, onLicensePageClick, t, locale, className, }) => {
1000
+ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, maxDailyUsage = 20, showUsage = true, showPlan = true, isLoading = false, onLicensePageClick, licenseSource, accessScope, expiresAt, lastVerifiedAt, t, locale, className, }) => {
986
1001
  const { t: translate } = useFigmaUiI18n(locale, t);
987
1002
  const [isOpen, setIsOpen] = useState(false);
988
1003
  const rootRef = useRef(null);
@@ -1005,9 +1020,12 @@ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, max
1005
1020
  : paymentStatus === 'TRIAL'
1006
1021
  ? fallbackText(translate, 'trialPlan', '체험판')
1007
1022
  : fallbackText(translate, 'free', '무료');
1008
- const planTitle = isBeta
1023
+ const planSuffix = fallbackText(translate, 'planSuffix', '플랜');
1024
+ const normalizedPlanLabel = planLabel.toLocaleLowerCase();
1025
+ const normalizedPlanSuffix = planSuffix.toLocaleLowerCase();
1026
+ const planTitle = isBeta || normalizedPlanLabel.includes(normalizedPlanSuffix)
1009
1027
  ? planLabel
1010
- : `${planLabel} ${fallbackText(translate, 'planSuffix', '플랜')}`;
1028
+ : `${planLabel} ${planSuffix}`;
1011
1029
  const triggerLabel = isPaid
1012
1030
  ? `${planLabel}, ${fallbackText(translate, 'unlimitedUsage', '무제한 사용 가능')}`
1013
1031
  : isBeta
@@ -1018,6 +1036,41 @@ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, max
1018
1036
  const upgradeLabel = isPaid
1019
1037
  ? fallbackText(translate, 'managePlan', '플랜 관리')
1020
1038
  : fallbackText(translate, 'upgradePlan', '플랜 업그레이드');
1039
+ const resolvedLocale = locale || 'ko';
1040
+ const formatDate = (value) => {
1041
+ if (!value)
1042
+ return undefined;
1043
+ const date = new Date(value);
1044
+ if (Number.isNaN(date.getTime()))
1045
+ return value;
1046
+ return new Intl.DateTimeFormat(resolvedLocale, { dateStyle: 'medium' }).format(date);
1047
+ };
1048
+ const formattedSource = licenseSource === 'youtube_membership' || licenseSource === 'membership'
1049
+ ? fallbackText(translate, 'license.source_youtube_membership', 'YouTube 멤버십')
1050
+ : licenseSource === 'lemonsqueezy'
1051
+ ? fallbackText(translate, 'license.source_purchase', '구매 라이선스')
1052
+ : licenseSource;
1053
+ const formattedScope = accessScope === 'all-products' || accessScope === 'all_products'
1054
+ ? fallbackText(translate, 'license.all_plugins', '모든 플러그인')
1055
+ : accessScope;
1056
+ const paidMetadata = [
1057
+ formattedSource && {
1058
+ label: fallbackText(translate, 'license.membership', '멤버십'),
1059
+ value: formattedSource,
1060
+ },
1061
+ expiresAt && {
1062
+ label: fallbackText(translate, 'license.expires', '만료일'),
1063
+ value: formatDate(expiresAt),
1064
+ },
1065
+ formattedScope && {
1066
+ label: fallbackText(translate, 'license.access', '접근 범위'),
1067
+ value: formattedScope,
1068
+ },
1069
+ lastVerifiedAt && {
1070
+ label: fallbackText(translate, 'license.last_checked', '최근 확인'),
1071
+ value: formatDate(lastVerifiedAt),
1072
+ },
1073
+ ].filter(Boolean);
1021
1074
  useEffect(() => {
1022
1075
  if (!isOpen)
1023
1076
  return undefined;
@@ -1052,7 +1105,10 @@ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, max
1052
1105
  'designbase-figma-usage-status--loading': isLoading,
1053
1106
  }, className) },
1054
1107
  React.createElement("div", { className: "designbase-figma-usage-status__control" },
1055
- showUsage && (React.createElement("button", { ref: triggerRef, type: "button", className: "designbase-figma-usage-status__trigger", "aria-expanded": isOpen, "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": isLoading ? fallbackText(translate, 'loading', '불러오는 중') : triggerLabel, disabled: isLoading, onClick: togglePopover }, isLoading ? (React.createElement("span", { className: "designbase-figma-usage-status__skeleton", "aria-hidden": "true" })) : isPaid ? (React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E")) : hasDailyLimit ? (React.createElement(React.Fragment, null,
1108
+ showUsage && (React.createElement("button", { ref: triggerRef, type: "button", className: "designbase-figma-usage-status__trigger", "aria-expanded": isOpen, "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": isLoading ? fallbackText(translate, 'loading', '불러오는 중') : triggerLabel, disabled: isLoading, onClick: togglePopover }, isLoading ? (React.createElement("span", { className: "designbase-figma-usage-status__skeleton", "aria-hidden": "true" })) : isPaid ? (React.createElement(React.Fragment, null,
1109
+ React.createElement("span", { className: "designbase-figma-usage-status__mini-progress", "aria-hidden": "true" },
1110
+ React.createElement("span", { style: { width: '100%' } })),
1111
+ React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E"))) : hasDailyLimit ? (React.createElement(React.Fragment, null,
1056
1112
  React.createElement("span", { className: "designbase-figma-usage-status__mini-progress", "aria-hidden": "true" },
1057
1113
  React.createElement("span", { style: { width: `${remainingPercent}%` } })),
1058
1114
  React.createElement("span", { className: "designbase-figma-usage-status__usage-value" },
@@ -1063,11 +1119,19 @@ const UsageStatusPopover = ({ paymentStatus, usageCount = 0, remainingUsage, max
1063
1119
  showPlan && !isLoading && (React.createElement("button", { type: "button", className: "designbase-figma-usage-status__plan-badge", "aria-expanded": isOpen, "aria-controls": popoverId, "aria-haspopup": "dialog", "aria-label": planTitle, onClick: togglePopover }, planLabel))),
1064
1120
  isOpen && (React.createElement("div", { id: popoverId, className: "designbase-figma-usage-status__popover", role: "dialog", "aria-label": fallbackText(translate, 'usageDetails', '사용량 상세') },
1065
1121
  React.createElement("div", { className: "designbase-figma-usage-status__popover-header" },
1066
- React.createElement("span", { className: "designbase-figma-usage-status__plan-title" }, planTitle),
1122
+ React.createElement("span", { className: "designbase-figma-usage-status__plan-heading" },
1123
+ React.createElement("span", null, fallbackText(translate, 'license.current_plan', '현재 플랜')),
1124
+ React.createElement("strong", null, planTitle)),
1067
1125
  !isBeta && (React.createElement(Button, { className: "designbase-figma-usage-status__upgrade", variant: isPaid ? 'secondary' : 'primary', size: "s", endIcon: "chevron-right", onPress: handleManageClick }, upgradeLabel))),
1068
- isPaid ? (React.createElement("div", { className: "designbase-figma-usage-status__unlimited-copy" },
1069
- React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E"),
1070
- React.createElement("span", null, fallbackText(translate, 'unlimitedUsageTooltip', '모든 기능을 제한 없이 사용할 수 있습니다')))) : isBeta ? (React.createElement("p", { className: "designbase-figma-usage-status__note" }, fallbackText(translate, 'betaUsageDescription', '베타 기간에는 사용량 제한이 적용되지 않습니다'))) : hasDailyLimit ? (React.createElement("div", { className: "designbase-figma-usage-status__usage-detail" },
1126
+ isPaid ? (React.createElement(React.Fragment, null,
1127
+ React.createElement("div", { className: "designbase-figma-usage-status__unlimited-copy" },
1128
+ React.createElement("span", { className: "designbase-figma-usage-status__unlimited-mark", "aria-hidden": "true" }, "\u221E"),
1129
+ React.createElement("span", null,
1130
+ React.createElement("strong", null, fallbackText(translate, 'unlimitedUsage', '무제한 사용')),
1131
+ React.createElement("small", null, fallbackText(translate, 'unlimitedUsageTooltip', '모든 기능을 제한 없이 사용할 수 있습니다')))),
1132
+ paidMetadata.length > 0 && (React.createElement("dl", { className: "designbase-figma-usage-status__license-meta" }, paidMetadata.map((item) => (React.createElement("div", { key: item.label },
1133
+ React.createElement("dt", null, item.label),
1134
+ React.createElement("dd", null, item.value)))))))) : isBeta ? (React.createElement("p", { className: "designbase-figma-usage-status__note" }, fallbackText(translate, 'betaUsageDescription', '베타 기간에는 사용량 제한이 적용되지 않습니다'))) : hasDailyLimit ? (React.createElement("div", { className: "designbase-figma-usage-status__usage-detail" },
1071
1135
  React.createElement("div", { className: "designbase-figma-usage-status__usage-summary" },
1072
1136
  React.createElement("strong", { className: "designbase-figma-usage-status__usage-count" },
1073
1137
  remaining,
@@ -1169,7 +1233,7 @@ FigmaToastProvider.displayName = 'FigmaToastProvider';
1169
1233
  FigmaToast.displayName = 'FigmaToast';
1170
1234
 
1171
1235
  const DEFAULT_LOW_USAGE_WARNING = { key: 'lowUsageWarning' };
1172
- const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount = 0, remainingUsage, listenForUsageMessages = true, isLoading = false, showPaymentStatus = true, showUsageInfo = true, maxDailyUsage = 20, showLowUsageWarning = true, lowUsageThresholdPercent = 10, lowUsageWarningText = DEFAULT_LOW_USAGE_WARNING, onLowUsageWarning, showPaymentBadge = true, showDonation = true, donationUrl = 'https://buymeacoffee.com/designbase', donationText = { key: 'donation.buy_me_a_coffee' }, showDonationText = true, donationPromptText, showLanguageSelector = true, currentLanguage = 'ko', languages, onLanguageChange, logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', logoLinks, className, t, children, }) => {
1236
+ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount = 0, remainingUsage, listenForUsageMessages = true, isLoading = false, showPaymentStatus = true, showUsageInfo = true, maxDailyUsage = 20, licenseSource, accessScope, expiresAt, lastVerifiedAt, showLowUsageWarning = true, lowUsageThresholdPercent = 10, lowUsageWarningText = DEFAULT_LOW_USAGE_WARNING, onLowUsageWarning, showPaymentBadge = true, showDonation = true, donationUrl = 'https://buymeacoffee.com/designbase', donationText = { key: 'donation.buy_me_a_coffee' }, showDonationText = true, donationPromptText, showLanguageSelector = true, currentLanguage = 'ko', languages, onLanguageChange, logoSrc, logoAlt = 'DesignBase', logoType = 'designbase', logoSize = 'xs', logoLinks, className, t, children, }) => {
1173
1237
  const { t: translate } = useFigmaUiI18n(currentLanguage, t);
1174
1238
  const { showToast, toastComponent } = useFigmaToast();
1175
1239
  const [liveUsage, setLiveUsage] = useState({});
@@ -1179,12 +1243,17 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
1179
1243
  const effectiveUsageCount = liveUsage.usageCount ?? usageCount;
1180
1244
  const effectiveRemainingUsage = liveUsage.remainingUsage ?? remainingUsage;
1181
1245
  const effectiveMaxDailyUsage = liveUsage.maxDailyUsage ?? maxDailyUsage;
1246
+ const effectiveLicenseSource = liveUsage.licenseSource ?? licenseSource;
1247
+ const effectiveAccessScope = liveUsage.accessScope ?? accessScope;
1248
+ const effectiveExpiresAt = liveUsage.expiresAt ?? expiresAt;
1249
+ const effectiveLastVerifiedAt = liveUsage.lastVerifiedAt ?? lastVerifiedAt;
1182
1250
  const limit = Math.max(1, Math.round(effectiveMaxDailyUsage));
1183
1251
  const resolvedRemaining = typeof effectiveRemainingUsage === 'number' && Number.isFinite(effectiveRemainingUsage)
1184
1252
  ? Math.min(limit, Math.max(0, Math.round(effectiveRemainingUsage)))
1185
1253
  : Math.max(0, limit - Math.min(limit, Math.max(0, Math.round(effectiveUsageCount))));
1186
1254
  const remainingPercent = Math.round((resolvedRemaining / limit) * 100);
1187
- const resolvedShowUsageInfo = showPaymentStatus && (showUsageInfo || effectivePaymentStatus === 'FREE');
1255
+ const resolvedShowUsageInfo = showPaymentStatus
1256
+ && (showUsageInfo || effectivePaymentStatus === 'FREE' || effectivePaymentStatus === 'PAID');
1188
1257
  const resolvedShowPaymentBadge = showPaymentStatus && showPaymentBadge;
1189
1258
  const resolvedLicensePageClick = effectivePaymentStatus === 'BETA' ? undefined : onLicensePageClick;
1190
1259
  const hasChildren = React.Children.count(children) > 0;
@@ -1210,10 +1279,31 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
1210
1279
  : message.paymentStatus === 'FREE' || message.paymentStatus === 'UNPAID'
1211
1280
  ? 'FREE'
1212
1281
  : undefined;
1282
+ const nextLicenseSource = typeof message.licenseSource === 'string' ? message.licenseSource : undefined;
1283
+ const nextAccessScope = typeof message.accessScope === 'string' ? message.accessScope : undefined;
1284
+ const nextExpiresAt = typeof message.expiresAt === 'string' ? message.expiresAt : undefined;
1285
+ const rawLastVerifiedAt = message.lastVerifiedAt;
1286
+ const numericLastVerifiedAt = typeof rawLastVerifiedAt === 'number' && Number.isFinite(rawLastVerifiedAt)
1287
+ ? rawLastVerifiedAt < 1000000000000
1288
+ ? rawLastVerifiedAt * 1000
1289
+ : rawLastVerifiedAt
1290
+ : undefined;
1291
+ const parsedLastVerifiedAt = numericLastVerifiedAt === undefined
1292
+ ? undefined
1293
+ : new Date(numericLastVerifiedAt);
1294
+ const nextLastVerifiedAt = typeof rawLastVerifiedAt === 'string'
1295
+ ? rawLastVerifiedAt
1296
+ : parsedLastVerifiedAt && !Number.isNaN(parsedLastVerifiedAt.getTime())
1297
+ ? parsedLastVerifiedAt.toISOString()
1298
+ : undefined;
1213
1299
  if (nextPaymentStatus === undefined
1214
1300
  && nextLimit === undefined
1215
1301
  && nextUsed === undefined
1216
- && nextRemaining === undefined) {
1302
+ && nextRemaining === undefined
1303
+ && nextLicenseSource === undefined
1304
+ && nextAccessScope === undefined
1305
+ && nextExpiresAt === undefined
1306
+ && nextLastVerifiedAt === undefined) {
1217
1307
  return;
1218
1308
  }
1219
1309
  setLiveUsage((previous) => ({
@@ -1222,6 +1312,10 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
1222
1312
  ...(typeof nextLimit === 'number' ? { maxDailyUsage: nextLimit } : {}),
1223
1313
  ...(typeof nextUsed === 'number' ? { usageCount: nextUsed } : {}),
1224
1314
  ...(typeof nextRemaining === 'number' ? { remainingUsage: nextRemaining } : {}),
1315
+ ...(nextLicenseSource ? { licenseSource: nextLicenseSource } : {}),
1316
+ ...(nextAccessScope ? { accessScope: nextAccessScope } : {}),
1317
+ ...(nextExpiresAt ? { expiresAt: nextExpiresAt } : {}),
1318
+ ...(nextLastVerifiedAt ? { lastVerifiedAt: nextLastVerifiedAt } : {}),
1225
1319
  }));
1226
1320
  };
1227
1321
  window.addEventListener('message', handleUsageMessage);
@@ -1280,7 +1374,7 @@ const FigmaFooter = ({ onLicensePageClick, paymentStatus = 'FREE', usageCount =
1280
1374
  React.createElement("div", { className: "designbase-figma-footer__left" },
1281
1375
  React.createElement(LogoDropdown, { logoSrc: logoSrc, logoAlt: logoAlt, logoType: logoType, logoSize: logoSize, links: logoLinks, languages: languages, currentLanguage: currentLanguage, showLanguageSelector: showLanguageSelector, onLanguageChange: onLanguageChange, t: translate })),
1282
1376
  React.createElement("div", { className: "designbase-figma-footer__right" },
1283
- (resolvedShowUsageInfo || resolvedShowPaymentBadge) && (React.createElement(UsageStatusPopover, { paymentStatus: effectivePaymentStatus, usageCount: effectiveUsageCount, remainingUsage: effectiveRemainingUsage, maxDailyUsage: effectiveMaxDailyUsage, showUsage: resolvedShowUsageInfo, showPlan: resolvedShowPaymentBadge, isLoading: isLoading, onLicensePageClick: resolvedLicensePageClick, t: translate })),
1377
+ (resolvedShowUsageInfo || resolvedShowPaymentBadge) && (React.createElement(UsageStatusPopover, { paymentStatus: effectivePaymentStatus, usageCount: effectiveUsageCount, remainingUsage: effectiveRemainingUsage, maxDailyUsage: effectiveMaxDailyUsage, showUsage: resolvedShowUsageInfo, showPlan: resolvedShowPaymentBadge, isLoading: isLoading, onLicensePageClick: resolvedLicensePageClick, licenseSource: effectiveLicenseSource, accessScope: effectiveAccessScope, expiresAt: effectiveExpiresAt, lastVerifiedAt: effectiveLastVerifiedAt, locale: currentLanguage, t: translate })),
1284
1378
  showDonation && (React.createElement(DonationBadge, { donationUrl: donationUrl, text: donationText, iconType: "heart", size: "s", iconOnly: true, showPrompt: false, t: translate })))),
1285
1379
  children,
1286
1380
  toastComponent));
@@ -1293,9 +1387,15 @@ const FormWithSubmit = ({ onLicenseSubmit, disabled = false, isSubmitting = fals
1293
1387
  useEffect(() => {
1294
1388
  setInputValue(value);
1295
1389
  }, [value]);
1390
+ const submitLicense = async () => {
1391
+ const normalizedValue = inputValue.trim();
1392
+ if (!normalizedValue || disabled || isSubmitting)
1393
+ return;
1394
+ await onLicenseSubmit(normalizedValue);
1395
+ };
1296
1396
  const handleSubmit = async (e) => {
1297
1397
  e.preventDefault();
1298
- await onLicenseSubmit(inputValue);
1398
+ await submitLicense();
1299
1399
  };
1300
1400
  const handleChange = (value) => {
1301
1401
  setInputValue(value);
@@ -1315,7 +1415,9 @@ const FormWithSubmit = ({ onLicenseSubmit, disabled = false, isSubmitting = fals
1315
1415
  handleChange(value);
1316
1416
  }
1317
1417
  }, placeholder: displayPlaceholder, helperText: displayHelperText, disabled: disabled || isSubmitting, size: "m" })),
1318
- React.createElement(Button, { type: "submit", variant: "primary", size: "m", loading: isSubmitting, disabled: !inputValue.trim() || isSubmitting || disabled, fullWidth: true }, isSubmitting ? displaySubmittingText : displaySubmitText)));
1418
+ React.createElement(Button, { type: "button", variant: "primary", size: "m", loading: isSubmitting, disabled: !inputValue.trim() || isSubmitting || disabled, fullWidth: true, onPress: () => {
1419
+ void submitLicense();
1420
+ } }, isSubmitting ? displaySubmittingText : displaySubmitText)));
1319
1421
  };
1320
1422
  FormWithSubmit.displayName = 'FormWithSubmit';
1321
1423
 
@@ -1438,7 +1540,7 @@ function formatSource(value, t) {
1438
1540
  }
1439
1541
  return value;
1440
1542
  }
1441
- const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSource, accessScope, expiresAt, lastVerifiedAt, validationMessage, allowRevealKey = false, t, locale, className, }) => {
1543
+ const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSource, accessScope, expiresAt, lastVerifiedAt, validationMessage, showHeader = true, allowRevealKey = false, t, locale, className, }) => {
1442
1544
  const { locale: resolvedLocale, t: translate } = useFigmaUiI18n(locale, t);
1443
1545
  const [isKeyRevealed, setIsKeyRevealed] = useState(false);
1444
1546
  const normalizedLicenseStatus = licenseStatus?.toLowerCase();
@@ -1449,13 +1551,15 @@ const LicenseStatusCard = ({ status, licenseKey = '', licenseStatus, licenseSour
1449
1551
  : resolveText(translate, { key: 'license.status_inactive' }, 'License check failed');
1450
1552
  const statusClass = isActive ? 'active' : 'inactive';
1451
1553
  return (React.createElement("section", { className: `designbase-figma-license-status-card designbase-figma-license-status-card--${statusClass} ${className || ''}`, "aria-label": statusText },
1452
- React.createElement("div", { className: "designbase-figma-license-status-card__header" },
1554
+ showHeader && (React.createElement("div", { className: "designbase-figma-license-status-card__header" },
1453
1555
  React.createElement("div", null,
1454
1556
  React.createElement("div", { className: "designbase-figma-license-status-card__eyebrow" }, isActive
1455
1557
  ? resolveText(translate, { key: 'license.activated' }, '라이선스 활성화됨')
1456
1558
  : resolveText(translate, { key: 'license.status_label' }, '라이선스 상태')),
1457
1559
  React.createElement("h3", null, statusText)),
1458
- React.createElement("span", { className: "designbase-figma-license-status-card__badge" }, licenseStatus || (isActive ? 'active' : 'inactive'))),
1560
+ React.createElement("span", { className: "designbase-figma-license-status-card__badge" }, isActive
1561
+ ? resolveText(translate, { key: 'license.status_active' }, '활성')
1562
+ : resolveText(translate, { key: 'license.status_inactive' }, '비활성')))),
1459
1563
  validationMessage && !isActive && (React.createElement("p", { className: "designbase-figma-license-status-card__message" }, validationMessage)),
1460
1564
  licenseKey && (React.createElement("div", { className: "designbase-figma-license-status-card__key-row" },
1461
1565
  React.createElement("span", null, resolveText(translate, { key: 'license.key_label' }, '라이선스 키')),
@@ -1850,7 +1954,7 @@ function formatUsageLabel(state, t) {
1850
1954
  }
1851
1955
  return resolveText(t, { key: 'license.usage_generic', values: { remaining, limit } }, `남은 횟수 ${remaining}/${limit}`) ?? `남은 횟수 ${remaining}/${limit}`;
1852
1956
  }
1853
- const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicenseKey = '', wordpressLinks, paymentPageUrl, mode = 'pricing', membershipUrl, enableAccountAuth = true, showLicenseKeyInput = true, pricingFeatures, pricing, paymentType = 'subscription', messageTypes, onStateChange, setPaymentStatus, setUsageCount, onLicenseSubmit, usageCount: initialUsageCount = 0, t, locale, className, }) => {
1957
+ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicenseKey = '', licenseStatus: initialLicenseStatus, licenseSource: initialLicenseSource, accessScope: initialAccessScope, expiresAt: initialExpiresAt, lastVerifiedAt: initialLastVerifiedAt, wordpressLinks, paymentPageUrl, mode = 'pricing', membershipUrl, enableAccountAuth = true, showLicenseKeyInput = true, pricingFeatures, pricing, paymentType = 'subscription', messageTypes, onStateChange, setPaymentStatus, setUsageCount, onLicenseSubmit, usageCount: initialUsageCount = 0, t, locale, className, }) => {
1854
1958
  const { t: translate } = useFigmaUiI18n(locale, t);
1855
1959
  const links = useMemo(() => ({
1856
1960
  purchaseUrl: wordpressLinks?.purchaseUrl ?? paymentPageUrl,
@@ -1863,13 +1967,21 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1863
1967
  initialState: {
1864
1968
  paymentStatus: initialStatus,
1865
1969
  licenseKey: initialLicenseKey,
1970
+ hasLicenseKey: Boolean(initialLicenseKey),
1971
+ isLicenseRegistered: initialStatus === 'PAID',
1972
+ planTier: initialStatus === 'PAID' ? 'PRO' : 'FREE',
1973
+ usageLimit: initialStatus === 'PAID' ? 'unlimited' : 0,
1974
+ licenseStatus: initialLicenseStatus,
1975
+ licenseSource: initialLicenseSource,
1976
+ accessScope: initialAccessScope,
1977
+ expiresAt: initialExpiresAt,
1978
+ lastVerifiedAt: initialLastVerifiedAt,
1866
1979
  usageCount: initialUsageCount,
1867
1980
  },
1868
1981
  onStateChange,
1869
1982
  onPaymentStatusChange: setPaymentStatus,
1870
1983
  onUsageCountChange: setUsageCount,
1871
1984
  });
1872
- const [showDetails, setShowDetails] = useState(false);
1873
1985
  const [licenseKeyDraft, setLicenseKeyDraft] = useState(initialLicenseKey);
1874
1986
  const isPaid = state.isLicenseRegistered && state.paymentStatus === 'PAID';
1875
1987
  const isMembershipMode = mode === 'youtube-membership';
@@ -1894,7 +2006,7 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1894
2006
  : isPaid
1895
2007
  ? resolveText(translate, { key: 'license.pro_account' }, '프로 계정')
1896
2008
  : resolveText(translate, { key: 'license.upgrade_to_pro' }, '프로로 업그레이드')),
1897
- state.planTier && state.planTier !== 'FREE' && (React.createElement("span", { className: "designbase-figma-page-license__tier" }, state.planTier))),
2009
+ isPaid && (React.createElement("span", { className: "designbase-figma-page-license__tier" }, resolveText(translate, { key: 'license.pro_plan' }, '프로 플랜')))),
1898
2010
  React.createElement("p", { className: "designbase-figma-page-license__description" }, isPaid
1899
2011
  ? resolveText(translate, { key: 'license.all_features_available' }, '모든 기능을 사용할 수 있습니다.')
1900
2012
  : isMembershipMode
@@ -1902,16 +2014,16 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1902
2014
  : resolveText(translate, { key: 'license.purchase_for_unlimited' }, '디자인베이스 멤버십 또는 라이선스로 무제한 이용이 가능합니다.')),
1903
2015
  !isPaid && !isMembershipMode && (React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel))),
1904
2016
  !isMembershipMode && (React.createElement(PricingComparison, { features: pricingFeatures, pricing: pricing, paymentType: paymentType, t: translate })),
1905
- state.lastMessage && !state.deactivateMessage && (React.createElement("p", { className: `designbase-figma-page-license__feedback ${state.lastErrorCode ? 'designbase-figma-page-license__feedback--error' : ''}`, "aria-live": "polite" }, state.lastMessage)),
1906
- isPaid && (React.createElement("div", { className: "designbase-figma-page-license__section" },
1907
- React.createElement("div", { className: "designbase-figma-page-license__section-header" },
1908
- React.createElement("h3", null, resolveText(translate, { key: 'license.activated' }, '라이선스 활성화됨')),
1909
- React.createElement(Button, { onPress: () => setShowDetails(!showDetails), variant: "tertiary", size: "s", iconOnly: true },
1910
- React.createElement(MoreHorizontalIcon, { size: 16 }))),
1911
- React.createElement(LicenseStatusCard, { status: state.paymentStatus, licenseKey: state.licenseKey, licenseStatus: state.licenseStatus, licenseSource: state.licenseSource, accessScope: state.accessScope, expiresAt: state.expiresAt, lastVerifiedAt: state.lastVerifiedAt, validationMessage: state.validationMessage, allowRevealKey: false, t: translate }),
1912
- React.createElement("p", { className: "designbase-figma-page-license__usage" }, usageLabel),
1913
- showDetails && (React.createElement("div", { className: "designbase-figma-page-license__details" },
1914
- React.createElement("div", { className: "designbase-figma-page-license__activation-info" },
2017
+ state.lastMessage && state.lastErrorCode && !state.deactivateMessage && (React.createElement("p", { className: `designbase-figma-page-license__feedback ${state.lastErrorCode ? 'designbase-figma-page-license__feedback--error' : ''}`, "aria-live": "polite" }, state.lastMessage)),
2018
+ isPaid && (React.createElement("div", { className: "designbase-figma-page-license__paid-summary" },
2019
+ React.createElement("div", { className: "designbase-figma-page-license__paid-hero" },
2020
+ React.createElement("span", { "aria-hidden": "true" }, "\u221E"),
2021
+ React.createElement("div", null,
2022
+ React.createElement("strong", null, resolveText(translate, { key: 'license.usage_unlimited' }, '무제한 사용')),
2023
+ React.createElement("p", null, resolveText(translate, { key: 'license.active_until_expiry' }, '만료일까지 모든 기능을 제한 없이 사용할 수 있습니다.')))),
2024
+ React.createElement(LicenseStatusCard, { status: state.paymentStatus, licenseKey: state.licenseKey, licenseStatus: state.licenseStatus, licenseSource: state.licenseSource, accessScope: state.accessScope, expiresAt: state.expiresAt, lastVerifiedAt: state.lastVerifiedAt, validationMessage: state.validationMessage, allowRevealKey: false, showHeader: false, t: translate }),
2025
+ React.createElement("div", { className: "designbase-figma-page-license__paid-actions" },
2026
+ state.activationLimit > 0 && (React.createElement("div", { className: "designbase-figma-page-license__activation-info" },
1915
2027
  React.createElement("div", { className: "designbase-figma-page-license__remaining" },
1916
2028
  Math.max(0, state.activationLimit - state.activationUsage),
1917
2029
  ' ',
@@ -1921,12 +2033,12 @@ const PageLicense = ({ status: initialStatus, onClose, licenseKey: initialLicens
1921
2033
  React.createElement("div", { className: "designbase-figma-page-license__usage-count" },
1922
2034
  state.activationUsage,
1923
2035
  "/",
1924
- state.activationLimit)),
1925
- React.createElement("div", { className: "designbase-figma-page-license__action" },
1926
- React.createElement(Button, { onPress: deactivateLicense, variant: "secondary", size: "s", fullWidth: true, disabled: isDeactivating }, isDeactivating
1927
- ? resolveText(translate, { key: 'license.deactivating' }, '비활성화 중...')
1928
- : resolveText(translate, { key: 'license.deactivate' }, '라이선스 비활성화')),
1929
- state.deactivateMessage && (React.createElement("p", { className: `designbase-figma-page-license__action-feedback designbase-figma-page-license__action-feedback--${resolvePaymentFeedbackVariant(state.deactivateErrorCode)}`, role: state.deactivateErrorCode ? 'alert' : 'status', "aria-live": "polite" }, state.deactivateMessage))))))),
2036
+ state.activationLimit))),
2037
+ React.createElement(Button, { onPress: deactivateLicense, variant: "secondary", size: "m", fullWidth: true, disabled: isDeactivating }, isDeactivating
2038
+ ? resolveText(translate, { key: 'license.deactivating' }, '비활성화 중...')
2039
+ : resolveText(translate, { key: 'license.deactivate' }, '라이선스 비활성화')),
2040
+ state.deactivateMessage && state.deactivateErrorCode && (React.createElement("p", { className: `designbase-figma-page-license__action-feedback designbase-figma-page-license__action-feedback--${resolvePaymentFeedbackVariant(state.deactivateErrorCode)}`, role: "alert", "aria-live": "polite" }, state.deactivateMessage)),
2041
+ React.createElement("p", { className: "designbase-figma-page-license__deactivate-hint" }, resolveText(translate, { key: 'license.deactivate_hint' }, '이 기기에서 라이선스 연결을 해제합니다.'))))),
1930
2042
  !isPaid && (React.createElement(React.Fragment, null,
1931
2043
  isMembershipMode && (React.createElement(LicenseOffer, { joinUrl: links.membershipUrl || links.purchaseUrl, guideUrl: links.helpUrl || links.purchaseUrl, t: translate })),
1932
2044
  !isMembershipMode && (enableAccountAuth || links.purchaseUrl || links.accountUrl) && (React.createElement("div", { className: "designbase-figma-page-license__section" },