@saasicat/ui-vue 0.18.0 → 0.19.0
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/{chunk-CC4WK3DC.js → chunk-DPTIOQXX.js} +2 -4
- package/dist/{chunk-VRCVJTY4.js → chunk-ORX3ZG47.js} +1 -1
- package/dist/{chunk-Y5MWMWMD.js → chunk-RVG6LQHL.js} +1 -1
- package/dist/client/index.cjs +2 -4
- package/dist/client/index.js +2 -2
- package/dist/index.cjs +4 -6
- package/dist/index.js +3 -3
- package/dist/quasar/index.cjs +3 -0
- package/dist/quasar/index.js +2 -2
- package/package.json +2 -2
- package/src/client/i18n/format.ts +7 -4
- package/src/components/dialogs/PilotCreateDialog.vue +15 -188
- package/src/components/dialogs/PilotEditDialog.vue +51 -233
- package/src/components/dialogs/PromoCodeCreateDialog.vue +19 -560
- package/src/components/dialogs/PromoCodeDialogFields.vue +666 -0
- package/src/components/dialogs/PromoCodeEditDialog.vue +22 -570
- package/src/components/dialogs/pilot-dialog.css +222 -0
|
@@ -22,244 +22,14 @@
|
|
|
22
22
|
</q-card-section>
|
|
23
23
|
|
|
24
24
|
<q-card-section class="pc-dlg__body">
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
v-model="form.code"
|
|
34
|
-
class="pc-input pc-input--code"
|
|
35
|
-
:placeholder="msg.form.codePlaceholder"
|
|
36
|
-
@input="onCodeInput"
|
|
37
|
-
/>
|
|
38
|
-
<button
|
|
39
|
-
type="button"
|
|
40
|
-
class="pc-btn-mini"
|
|
41
|
-
@click="form.code = randomCode()"
|
|
42
|
-
>
|
|
43
|
-
{{ msg.form.codeRandom }}
|
|
44
|
-
</button>
|
|
45
|
-
</div>
|
|
46
|
-
<div class="pc-field__hint">{{ msg.form.codeHint }}</div>
|
|
47
|
-
</div>
|
|
48
|
-
|
|
49
|
-
<div class="pc-field">
|
|
50
|
-
<div class="pc-field__label">{{ msg.form.valueTypeLabel }}</div>
|
|
51
|
-
<div class="pc-type-grid">
|
|
52
|
-
<button
|
|
53
|
-
v-for="o in typeOptions"
|
|
54
|
-
:key="o.k"
|
|
55
|
-
type="button"
|
|
56
|
-
class="pc-type-opt"
|
|
57
|
-
:class="{ 'pc-type-opt--active': form.valueType === o.k }"
|
|
58
|
-
@click="form.valueType = o.k"
|
|
59
|
-
>
|
|
60
|
-
<div class="pc-type-opt__label">{{ o.label }}</div>
|
|
61
|
-
<div class="pc-type-opt__sub">{{ o.sub }}</div>
|
|
62
|
-
</button>
|
|
63
|
-
</div>
|
|
64
|
-
</div>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div class="pc-field" style="max-width: 280px">
|
|
68
|
-
<div class="pc-field__label">
|
|
69
|
-
{{
|
|
70
|
-
form.valueType === 'PERCENT'
|
|
71
|
-
? msg.form.valuePercentLabel
|
|
72
|
-
: msg.form.valueAbsoluteLabel
|
|
73
|
-
}}
|
|
74
|
-
</div>
|
|
75
|
-
<input
|
|
76
|
-
v-model.number="form.value"
|
|
77
|
-
class="pc-input"
|
|
78
|
-
type="number"
|
|
79
|
-
min="1"
|
|
80
|
-
:max="form.valueType === 'PERCENT' ? 100 : undefined"
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
<!-- Section: Validity & Duration -->
|
|
86
|
-
<div class="pc-section">
|
|
87
|
-
<div class="pc-section__title">{{ msg.form.sectionValidity }}</div>
|
|
88
|
-
|
|
89
|
-
<div v-if="plans.length > 0" class="pc-field">
|
|
90
|
-
<div class="pc-field__label">{{ msg.form.plansLabel }}</div>
|
|
91
|
-
<div class="pc-plan-pick">
|
|
92
|
-
<button
|
|
93
|
-
v-for="p in plans"
|
|
94
|
-
:key="p.key"
|
|
95
|
-
type="button"
|
|
96
|
-
class="pc-plan-opt"
|
|
97
|
-
:class="{ 'pc-plan-opt--on': isPlanSelected(p.key) }"
|
|
98
|
-
:style="planChipStyle(p)"
|
|
99
|
-
@click="togglePlan(p.key)"
|
|
100
|
-
>
|
|
101
|
-
<span
|
|
102
|
-
class="pc-plan-opt__mark"
|
|
103
|
-
:style="{ background: p.color ?? '#64748b' }"
|
|
104
|
-
/>
|
|
105
|
-
{{ p.label }}
|
|
106
|
-
</button>
|
|
107
|
-
</div>
|
|
108
|
-
<div class="pc-field__hint">
|
|
109
|
-
{{
|
|
110
|
-
formatMessage(msg.form.plansHint, {
|
|
111
|
-
count: form.appliesToPlans.length,
|
|
112
|
-
})
|
|
113
|
-
}}
|
|
114
|
-
</div>
|
|
115
|
-
</div>
|
|
116
|
-
|
|
117
|
-
<div class="pc-grid pc-grid--2">
|
|
118
|
-
<div class="pc-field">
|
|
119
|
-
<div class="pc-field__label">{{ msg.form.durationLabel }}</div>
|
|
120
|
-
<div class="pc-dur">
|
|
121
|
-
<button
|
|
122
|
-
v-for="o in durationOptions"
|
|
123
|
-
:key="o.k"
|
|
124
|
-
type="button"
|
|
125
|
-
class="pc-dur-opt"
|
|
126
|
-
:class="{ 'pc-dur-opt--active': form.durationType === o.k }"
|
|
127
|
-
@click="form.durationType = o.k"
|
|
128
|
-
>
|
|
129
|
-
{{ o.label }}
|
|
130
|
-
</button>
|
|
131
|
-
</div>
|
|
132
|
-
<input
|
|
133
|
-
v-if="form.durationType !== 'ONCE'"
|
|
134
|
-
v-model.number="form.durationValue"
|
|
135
|
-
class="pc-input"
|
|
136
|
-
type="number"
|
|
137
|
-
min="1"
|
|
138
|
-
style="margin-top: 8px; max-width: 120px"
|
|
139
|
-
:placeholder="
|
|
140
|
-
form.durationType === 'MONTHS'
|
|
141
|
-
? msg.form.durationMonthsPlaceholder
|
|
142
|
-
: msg.form.durationCyclesPlaceholder
|
|
143
|
-
"
|
|
144
|
-
/>
|
|
145
|
-
</div>
|
|
146
|
-
|
|
147
|
-
<div class="pc-field">
|
|
148
|
-
<div class="pc-field__label">{{ msg.form.maxRedemptionsLabel }}</div>
|
|
149
|
-
<input
|
|
150
|
-
v-model.number="form.maxRedemptions"
|
|
151
|
-
class="pc-input"
|
|
152
|
-
type="number"
|
|
153
|
-
min="1"
|
|
154
|
-
:placeholder="msg.form.maxRedemptionsPlaceholder"
|
|
155
|
-
/>
|
|
156
|
-
<div class="pc-field__hint">
|
|
157
|
-
{{ msg.form.maxRedemptionsHintCreate }}
|
|
158
|
-
</div>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div class="pc-grid pc-grid--2">
|
|
163
|
-
<div class="pc-field">
|
|
164
|
-
<div class="pc-field__label">{{ msg.form.validFromLabel }}</div>
|
|
165
|
-
<input v-model="form.validFrom" class="pc-input" type="date" />
|
|
166
|
-
</div>
|
|
167
|
-
<div class="pc-field">
|
|
168
|
-
<div class="pc-field__label">{{ msg.form.validUntilLabel }}</div>
|
|
169
|
-
<input v-model="form.validUntil" class="pc-input" type="date" />
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
<!-- Section: Campaign & Note -->
|
|
175
|
-
<div class="pc-section">
|
|
176
|
-
<div class="pc-section__title">{{ msg.form.sectionCampaign }}</div>
|
|
177
|
-
<div v-if="showCampaignTag" class="pc-field">
|
|
178
|
-
<div class="pc-field__label">{{ msg.form.campaignLabel }}</div>
|
|
179
|
-
<input
|
|
180
|
-
v-model="form.campaignTag"
|
|
181
|
-
class="pc-input"
|
|
182
|
-
:placeholder="msg.form.campaignPlaceholder"
|
|
183
|
-
/>
|
|
184
|
-
<div class="pc-field__hint">{{ msg.form.campaignHint }}</div>
|
|
185
|
-
</div>
|
|
186
|
-
<div class="pc-field">
|
|
187
|
-
<div class="pc-field__label">{{ msg.form.noteLabel }}</div>
|
|
188
|
-
<textarea
|
|
189
|
-
v-model="form.description"
|
|
190
|
-
class="pc-input"
|
|
191
|
-
rows="2"
|
|
192
|
-
:placeholder="msg.form.notePlaceholder"
|
|
193
|
-
/>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
|
|
197
|
-
<!-- Section: Advanced (backend-only fields, collapsed) -->
|
|
198
|
-
<div class="pc-section">
|
|
199
|
-
<button
|
|
200
|
-
type="button"
|
|
201
|
-
class="pc-section__toggle"
|
|
202
|
-
@click="advancedOpen = !advancedOpen"
|
|
203
|
-
>
|
|
204
|
-
<q-icon
|
|
205
|
-
:name="advancedOpen ? 'expand_more' : 'chevron_right'"
|
|
206
|
-
size="16px"
|
|
207
|
-
/>
|
|
208
|
-
{{ msg.form.advancedToggle }}
|
|
209
|
-
</button>
|
|
210
|
-
<div v-if="advancedOpen" class="pc-advanced">
|
|
211
|
-
<div class="pc-grid pc-grid--2">
|
|
212
|
-
<div class="pc-field">
|
|
213
|
-
<div class="pc-field__label">{{ msg.form.billingCycleLabel }}</div>
|
|
214
|
-
<select v-model="form.appliesToBilling" class="pc-input">
|
|
215
|
-
<option :value="undefined">
|
|
216
|
-
{{ common.both }}
|
|
217
|
-
</option>
|
|
218
|
-
<option value="MONTHLY">{{ common.monthly }}</option>
|
|
219
|
-
<option value="YEARLY">{{ common.yearly }}</option>
|
|
220
|
-
</select>
|
|
221
|
-
</div>
|
|
222
|
-
<div class="pc-field">
|
|
223
|
-
<div class="pc-field__label">{{ msg.form.minAmountLabel }}</div>
|
|
224
|
-
<input
|
|
225
|
-
v-model.number="form.minimumPlanAmountGross"
|
|
226
|
-
class="pc-input"
|
|
227
|
-
type="number"
|
|
228
|
-
min="0"
|
|
229
|
-
:placeholder="msg.form.minAmountPlaceholder"
|
|
230
|
-
/>
|
|
231
|
-
</div>
|
|
232
|
-
</div>
|
|
233
|
-
<div class="pc-grid pc-grid--2">
|
|
234
|
-
<label class="pc-check">
|
|
235
|
-
<input v-model="form.firstTimeCustomersOnly" type="checkbox" />
|
|
236
|
-
<span>{{ msg.form.firstTimeOnly }}</span>
|
|
237
|
-
</label>
|
|
238
|
-
<label class="pc-check">
|
|
239
|
-
<input v-model="form.allowZeroInvoice" type="checkbox" />
|
|
240
|
-
<span>{{ msg.form.allowZeroInvoice }}</span>
|
|
241
|
-
</label>
|
|
242
|
-
</div>
|
|
243
|
-
<div class="pc-field">
|
|
244
|
-
<div class="pc-field__label">{{ msg.form.revenueAccountLabel }}</div>
|
|
245
|
-
<input
|
|
246
|
-
v-model="form.revenueDeductionAccount"
|
|
247
|
-
class="pc-input"
|
|
248
|
-
:placeholder="msg.form.revenueAccountPlaceholder"
|
|
249
|
-
/>
|
|
250
|
-
</div>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
253
|
-
|
|
254
|
-
<!-- Live preview -->
|
|
255
|
-
<div class="pc-preview">
|
|
256
|
-
<div class="pc-preview__eyebrow">{{ msg.form.previewEyebrow }}</div>
|
|
257
|
-
<div class="pc-preview__body">
|
|
258
|
-
<code class="pc-preview__code">{{ form.code || 'CODE' }}</code>
|
|
259
|
-
<span class="pc-preview__disc">{{ previewValue }}</span>
|
|
260
|
-
<span class="pc-preview__meta">{{ previewMeta }}</span>
|
|
261
|
-
</div>
|
|
262
|
-
</div>
|
|
25
|
+
<PromoCodeDialogFields
|
|
26
|
+
mode="create"
|
|
27
|
+
:form="form"
|
|
28
|
+
v-model:code="form.code"
|
|
29
|
+
v-model:advanced-open="advancedOpen"
|
|
30
|
+
:show-campaign-tag="showCampaignTag"
|
|
31
|
+
:plans="plans"
|
|
32
|
+
/>
|
|
263
33
|
|
|
264
34
|
<p v-if="error" class="pc-error">{{ error }}</p>
|
|
265
35
|
</q-card-section>
|
|
@@ -281,8 +51,8 @@
|
|
|
281
51
|
|
|
282
52
|
<script setup lang="ts">
|
|
283
53
|
import { computed, reactive, ref, watch } from 'vue';
|
|
284
|
-
import { formatMessage } from '../../client/i18n/format.js';
|
|
285
54
|
import { useSaMessages } from '../../vue/use-super-admin-i18n.js';
|
|
55
|
+
import PromoCodeDialogFields from './PromoCodeDialogFields.vue';
|
|
286
56
|
import type {
|
|
287
57
|
PromoCodeCreatePayload,
|
|
288
58
|
PromoCodeDurationType,
|
|
@@ -290,10 +60,10 @@ import type {
|
|
|
290
60
|
PromoCodeValueType,
|
|
291
61
|
} from './types.js';
|
|
292
62
|
|
|
293
|
-
// Platform promo-code create dialog
|
|
294
|
-
// picker, live preview
|
|
295
|
-
//
|
|
296
|
-
//
|
|
63
|
+
// Platform promo-code create dialog. The form body — sections, type grid, plan
|
|
64
|
+
// picker, live preview — is shared with the edit dialog through
|
|
65
|
+
// PromoCodeDialogFields; what stays here is the create-specific state and the
|
|
66
|
+
// payload the app's submit handler receives.
|
|
297
67
|
|
|
298
68
|
const props = withDefaults(
|
|
299
69
|
defineProps<{
|
|
@@ -317,27 +87,6 @@ const emit = defineEmits<{
|
|
|
317
87
|
const msg = useSaMessages('promos');
|
|
318
88
|
const common = useSaMessages('common');
|
|
319
89
|
|
|
320
|
-
const typeOptions = computed<ReadonlyArray<{ k: PromoCodeValueType; label: string; sub: string }>>(
|
|
321
|
-
() => [
|
|
322
|
-
{
|
|
323
|
-
k: 'PERCENT',
|
|
324
|
-
label: msg.value.form.valueTypePercent,
|
|
325
|
-
sub: msg.value.form.valueTypePercentSub,
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
k: 'ABSOLUTE',
|
|
329
|
-
label: msg.value.form.valueTypeAbsolute,
|
|
330
|
-
sub: msg.value.form.valueTypeAbsoluteSub,
|
|
331
|
-
},
|
|
332
|
-
],
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
const durationOptions = computed<ReadonlyArray<{ k: PromoCodeDurationType; label: string }>>(() => [
|
|
336
|
-
{ k: 'ONCE', label: msg.value.form.durationOnce },
|
|
337
|
-
{ k: 'MONTHS', label: msg.value.form.durationMonths },
|
|
338
|
-
{ k: 'BILLING_CYCLES', label: msg.value.form.durationBillingCycles },
|
|
339
|
-
]);
|
|
340
|
-
|
|
341
90
|
function emptyForm() {
|
|
342
91
|
return {
|
|
343
92
|
code: '',
|
|
@@ -364,60 +113,6 @@ const loading = ref(false);
|
|
|
364
113
|
const error = ref('');
|
|
365
114
|
const advancedOpen = ref(false);
|
|
366
115
|
|
|
367
|
-
function onCodeInput(): void {
|
|
368
|
-
form.code = form.code.toUpperCase().replace(/[^A-Z0-9_-]/g, '');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
function randomCode(): string {
|
|
372
|
-
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
|
373
|
-
let s = '';
|
|
374
|
-
for (let i = 0; i < 8; i++) s += chars[Math.floor(Math.random() * chars.length)];
|
|
375
|
-
return s;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function isPlanSelected(key: string): boolean {
|
|
379
|
-
return form.appliesToPlans.includes(key);
|
|
380
|
-
}
|
|
381
|
-
function togglePlan(key: string): void {
|
|
382
|
-
if (form.appliesToPlans.includes(key)) {
|
|
383
|
-
form.appliesToPlans = form.appliesToPlans.filter((k) => k !== key);
|
|
384
|
-
} else {
|
|
385
|
-
form.appliesToPlans = [...form.appliesToPlans, key];
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
function planChipStyle(p: PromoCodePlanOption): Record<string, string> {
|
|
389
|
-
if (!isPlanSelected(p.key) || !p.color) return {};
|
|
390
|
-
return {
|
|
391
|
-
borderColor: p.color,
|
|
392
|
-
background: `${p.color}12`,
|
|
393
|
-
color: p.color,
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const previewValue = computed(() => {
|
|
398
|
-
if (form.valueType === 'PERCENT') return `−${form.value || 0}%`;
|
|
399
|
-
return `−${form.value || 0} €`;
|
|
400
|
-
});
|
|
401
|
-
|
|
402
|
-
const previewMeta = computed(() => {
|
|
403
|
-
const parts: string[] = [];
|
|
404
|
-
const count = form.durationValue || 0;
|
|
405
|
-
if (form.durationType === 'ONCE') parts.push(msg.value.form.previewOnce);
|
|
406
|
-
else if (form.durationType === 'MONTHS')
|
|
407
|
-
parts.push(formatMessage(msg.value.form.previewMonths, { count }));
|
|
408
|
-
else parts.push(formatMessage(msg.value.form.previewCycles, { count }));
|
|
409
|
-
parts.push(
|
|
410
|
-
form.appliesToPlans.length > 0
|
|
411
|
-
? form.appliesToPlans.join(', ')
|
|
412
|
-
: props.plans.length > 0
|
|
413
|
-
? msg.value.form.previewAllPlans
|
|
414
|
-
: msg.value.form.previewNoPlanFilter,
|
|
415
|
-
);
|
|
416
|
-
if (form.maxRedemptions)
|
|
417
|
-
parts.push(formatMessage(msg.value.form.previewMax, { count: form.maxRedemptions }));
|
|
418
|
-
return parts.join(' · ');
|
|
419
|
-
});
|
|
420
|
-
|
|
421
116
|
const isValid = computed(() => {
|
|
422
117
|
if (!/^[A-Z0-9_-]{3,32}$/.test(form.code)) return false;
|
|
423
118
|
if (!form.value || form.value <= 0) return false;
|
|
@@ -479,278 +174,42 @@ async function onSubmit() {
|
|
|
479
174
|
min-width: 720px;
|
|
480
175
|
max-width: 96vw;
|
|
481
176
|
}
|
|
177
|
+
|
|
482
178
|
.pc-dlg__head {
|
|
483
179
|
display: flex;
|
|
484
180
|
align-items: flex-start;
|
|
485
181
|
gap: 16px;
|
|
486
182
|
padding-bottom: 8px;
|
|
487
183
|
}
|
|
184
|
+
|
|
488
185
|
.pc-dlg__close {
|
|
489
186
|
margin-left: auto;
|
|
490
187
|
}
|
|
188
|
+
|
|
491
189
|
.pc-dlg__title {
|
|
492
190
|
font-family: var(--sa-font-head, system-ui, sans-serif);
|
|
493
191
|
font-weight: 700;
|
|
494
192
|
font-size: 18px;
|
|
495
193
|
color: var(--sa-heading, #0f172a);
|
|
496
194
|
}
|
|
195
|
+
|
|
497
196
|
.pc-dlg__sub {
|
|
498
197
|
font-size: 12.5px;
|
|
499
198
|
color: var(--sa-muted, #64748b);
|
|
500
199
|
margin-top: 2px;
|
|
501
200
|
}
|
|
201
|
+
|
|
502
202
|
.pc-dlg__body {
|
|
503
203
|
display: flex;
|
|
504
204
|
flex-direction: column;
|
|
505
205
|
gap: 16px;
|
|
506
206
|
padding-top: 4px;
|
|
507
207
|
}
|
|
208
|
+
|
|
508
209
|
.pc-dlg__foot {
|
|
509
210
|
border-top: 1px solid var(--sa-border, #e2e8f0);
|
|
510
211
|
}
|
|
511
212
|
|
|
512
|
-
.pc-section {
|
|
513
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
514
|
-
border-radius: 10px;
|
|
515
|
-
padding: 14px 16px;
|
|
516
|
-
background: #fafbfc;
|
|
517
|
-
}
|
|
518
|
-
.pc-section__title {
|
|
519
|
-
font-family: var(--sa-font-head, system-ui, sans-serif);
|
|
520
|
-
font-weight: 700;
|
|
521
|
-
font-size: 13px;
|
|
522
|
-
color: var(--sa-heading, #0f172a);
|
|
523
|
-
margin-bottom: 12px;
|
|
524
|
-
letter-spacing: -0.005em;
|
|
525
|
-
}
|
|
526
|
-
.pc-section__toggle {
|
|
527
|
-
border: 0;
|
|
528
|
-
background: transparent;
|
|
529
|
-
cursor: pointer;
|
|
530
|
-
font: 600 12.5px var(--sa-font-body, system-ui, sans-serif);
|
|
531
|
-
color: var(--sa-muted-dark, #475569);
|
|
532
|
-
display: inline-flex;
|
|
533
|
-
align-items: center;
|
|
534
|
-
gap: 4px;
|
|
535
|
-
padding: 0;
|
|
536
|
-
}
|
|
537
|
-
.pc-advanced {
|
|
538
|
-
display: flex;
|
|
539
|
-
flex-direction: column;
|
|
540
|
-
gap: 12px;
|
|
541
|
-
margin-top: 12px;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
.pc-grid {
|
|
545
|
-
display: grid;
|
|
546
|
-
gap: 12px;
|
|
547
|
-
margin-bottom: 12px;
|
|
548
|
-
}
|
|
549
|
-
.pc-grid--2 {
|
|
550
|
-
grid-template-columns: 1fr 1fr;
|
|
551
|
-
}
|
|
552
|
-
@media (max-width: 600px) {
|
|
553
|
-
.pc-grid--2 {
|
|
554
|
-
grid-template-columns: 1fr;
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
.pc-grid:last-child {
|
|
558
|
-
margin-bottom: 0;
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
.pc-field {
|
|
562
|
-
display: flex;
|
|
563
|
-
flex-direction: column;
|
|
564
|
-
gap: 4px;
|
|
565
|
-
}
|
|
566
|
-
.pc-field__label {
|
|
567
|
-
font-size: 11.5px;
|
|
568
|
-
font-weight: 600;
|
|
569
|
-
color: var(--sa-muted, #64748b);
|
|
570
|
-
text-transform: uppercase;
|
|
571
|
-
letter-spacing: 0.04em;
|
|
572
|
-
}
|
|
573
|
-
.pc-field__hint {
|
|
574
|
-
font-size: 11.5px;
|
|
575
|
-
color: #94a3b8;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.pc-input {
|
|
579
|
-
width: 100%;
|
|
580
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
581
|
-
background: #fff;
|
|
582
|
-
border-radius: 7px;
|
|
583
|
-
padding: 8px 10px;
|
|
584
|
-
font: 13.5px var(--sa-font-body, system-ui, sans-serif);
|
|
585
|
-
color: var(--sa-body, #1e293b);
|
|
586
|
-
outline: 0;
|
|
587
|
-
}
|
|
588
|
-
.pc-input:focus {
|
|
589
|
-
border-color: var(--sa-primary, #3f6bff);
|
|
590
|
-
box-shadow: 0 0 0 3px rgba(63, 107, 255, 0.12);
|
|
591
|
-
}
|
|
592
|
-
.pc-input--code {
|
|
593
|
-
font: 600 14px var(--sa-font-mono, ui-monospace, monospace);
|
|
594
|
-
letter-spacing: 0.04em;
|
|
595
|
-
text-transform: uppercase;
|
|
596
|
-
}
|
|
597
|
-
textarea.pc-input {
|
|
598
|
-
font: 13.5px var(--sa-font-body, system-ui, sans-serif);
|
|
599
|
-
resize: vertical;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
.pc-code-input {
|
|
603
|
-
display: flex;
|
|
604
|
-
gap: 8px;
|
|
605
|
-
align-items: center;
|
|
606
|
-
}
|
|
607
|
-
.pc-btn-mini {
|
|
608
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
609
|
-
background: #fff;
|
|
610
|
-
border-radius: 7px;
|
|
611
|
-
padding: 6px 10px;
|
|
612
|
-
font: 500 12px var(--sa-font-body, system-ui, sans-serif);
|
|
613
|
-
cursor: pointer;
|
|
614
|
-
color: var(--sa-muted-dark, #475569);
|
|
615
|
-
}
|
|
616
|
-
.pc-btn-mini:hover {
|
|
617
|
-
background: #f1f5f9;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
.pc-type-grid {
|
|
621
|
-
display: grid;
|
|
622
|
-
grid-template-columns: 1fr 1fr;
|
|
623
|
-
gap: 6px;
|
|
624
|
-
}
|
|
625
|
-
.pc-type-opt {
|
|
626
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
627
|
-
background: #fff;
|
|
628
|
-
border-radius: 8px;
|
|
629
|
-
padding: 8px 10px;
|
|
630
|
-
text-align: left;
|
|
631
|
-
cursor: pointer;
|
|
632
|
-
transition:
|
|
633
|
-
border-color 0.1s,
|
|
634
|
-
background 0.1s;
|
|
635
|
-
}
|
|
636
|
-
.pc-type-opt:hover {
|
|
637
|
-
border-color: #cbd5e1;
|
|
638
|
-
}
|
|
639
|
-
.pc-type-opt--active {
|
|
640
|
-
border-color: var(--sa-primary, #3f6bff);
|
|
641
|
-
background: var(--sa-primary-soft, rgba(63, 107, 255, 0.08));
|
|
642
|
-
}
|
|
643
|
-
.pc-type-opt__label {
|
|
644
|
-
font: 600 12.5px var(--sa-font-body, system-ui, sans-serif);
|
|
645
|
-
color: var(--sa-heading, #0f172a);
|
|
646
|
-
}
|
|
647
|
-
.pc-type-opt__sub {
|
|
648
|
-
font: 11.5px var(--sa-font-mono, ui-monospace, monospace);
|
|
649
|
-
color: var(--sa-muted, #64748b);
|
|
650
|
-
margin-top: 1px;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
.pc-plan-pick {
|
|
654
|
-
display: flex;
|
|
655
|
-
flex-wrap: wrap;
|
|
656
|
-
gap: 6px;
|
|
657
|
-
}
|
|
658
|
-
.pc-plan-opt {
|
|
659
|
-
display: inline-flex;
|
|
660
|
-
align-items: center;
|
|
661
|
-
gap: 8px;
|
|
662
|
-
background: #fff;
|
|
663
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
664
|
-
border-radius: 999px;
|
|
665
|
-
padding: 5px 12px 5px 8px;
|
|
666
|
-
font: 600 12px var(--sa-font-body, system-ui, sans-serif);
|
|
667
|
-
cursor: pointer;
|
|
668
|
-
color: var(--sa-muted-dark, #475569);
|
|
669
|
-
transition:
|
|
670
|
-
border-color 0.1s,
|
|
671
|
-
background 0.1s;
|
|
672
|
-
}
|
|
673
|
-
.pc-plan-opt:hover {
|
|
674
|
-
border-color: #cbd5e1;
|
|
675
|
-
}
|
|
676
|
-
.pc-plan-opt--on {
|
|
677
|
-
border-color: var(--sa-primary, #3f6bff);
|
|
678
|
-
background: var(--sa-primary-soft, rgba(63, 107, 255, 0.08));
|
|
679
|
-
color: var(--sa-primary, #3f6bff);
|
|
680
|
-
}
|
|
681
|
-
.pc-plan-opt__mark {
|
|
682
|
-
width: 8px;
|
|
683
|
-
height: 8px;
|
|
684
|
-
border-radius: 50%;
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
.pc-dur {
|
|
688
|
-
display: flex;
|
|
689
|
-
gap: 4px;
|
|
690
|
-
}
|
|
691
|
-
.pc-dur-opt {
|
|
692
|
-
flex: 1;
|
|
693
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
694
|
-
background: #fff;
|
|
695
|
-
border-radius: 7px;
|
|
696
|
-
padding: 6px 10px;
|
|
697
|
-
font: 500 12px var(--sa-font-body, system-ui, sans-serif);
|
|
698
|
-
cursor: pointer;
|
|
699
|
-
color: var(--sa-muted-dark, #475569);
|
|
700
|
-
}
|
|
701
|
-
.pc-dur-opt--active {
|
|
702
|
-
border-color: var(--sa-primary, #3f6bff);
|
|
703
|
-
background: var(--sa-primary-soft, rgba(63, 107, 255, 0.08));
|
|
704
|
-
color: var(--sa-primary, #3f6bff);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
.pc-check {
|
|
708
|
-
display: inline-flex;
|
|
709
|
-
align-items: center;
|
|
710
|
-
gap: 8px;
|
|
711
|
-
font: 13px var(--sa-font-body, system-ui, sans-serif);
|
|
712
|
-
color: var(--sa-body, #1e293b);
|
|
713
|
-
cursor: pointer;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
.pc-preview {
|
|
717
|
-
background: var(--sa-primary-soft, rgba(63, 107, 255, 0.06));
|
|
718
|
-
border: 1px solid var(--sa-primary-border, rgba(63, 107, 255, 0.18));
|
|
719
|
-
border-radius: 10px;
|
|
720
|
-
padding: 12px 14px;
|
|
721
|
-
}
|
|
722
|
-
.pc-preview__eyebrow {
|
|
723
|
-
font-size: 11px;
|
|
724
|
-
font-weight: 700;
|
|
725
|
-
color: var(--sa-primary, #3f6bff);
|
|
726
|
-
text-transform: uppercase;
|
|
727
|
-
letter-spacing: 0.06em;
|
|
728
|
-
margin-bottom: 6px;
|
|
729
|
-
}
|
|
730
|
-
.pc-preview__body {
|
|
731
|
-
display: flex;
|
|
732
|
-
align-items: center;
|
|
733
|
-
gap: 10px;
|
|
734
|
-
flex-wrap: wrap;
|
|
735
|
-
}
|
|
736
|
-
.pc-preview__code {
|
|
737
|
-
background: #fff;
|
|
738
|
-
border: 1px solid var(--sa-border, #e2e8f0);
|
|
739
|
-
border-radius: 6px;
|
|
740
|
-
padding: 3px 8px;
|
|
741
|
-
font: 600 13px var(--sa-font-mono, ui-monospace, monospace);
|
|
742
|
-
letter-spacing: 0.04em;
|
|
743
|
-
color: var(--sa-heading, #0f172a);
|
|
744
|
-
}
|
|
745
|
-
.pc-preview__disc {
|
|
746
|
-
font: 700 13px var(--sa-font-body, system-ui, sans-serif);
|
|
747
|
-
color: var(--sa-positive, #047857);
|
|
748
|
-
}
|
|
749
|
-
.pc-preview__meta {
|
|
750
|
-
font-size: 12px;
|
|
751
|
-
color: var(--sa-muted, #64748b);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
213
|
.pc-error {
|
|
755
214
|
background: #fef2f2;
|
|
756
215
|
border: 1px solid #fecaca;
|