@tapni/auth 1.0.5 → 1.0.6-3.dev
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/README.md +2 -0
- package/dist/.vite/manifest.json +16 -43
- package/dist/.well-known/assetlinks.json +10 -12
- package/dist/.well-known/microsoft-identity-association.json +5 -5
- package/dist/{Apps-DMds3Dv-.js → Apps-XNA4_3B4.js} +34 -34
- package/dist/Billing-Br0-fHed.js +256 -0
- package/dist/CustomApp-CrlbYDOm.js +83 -0
- package/dist/QR-Bvqb60-E.js +41 -0
- package/dist/TapniAuth.es.js +1 -1
- package/dist/TapniAuth.umd.js +49 -23
- package/dist/{install-L-cxSovH.js → install-7FOVy8Ol.js} +6267 -4743
- package/dist/site.webmanifest +11 -1
- package/dist/style.css +1 -1
- package/dist/{web-IFGkBi0t.js → web-UrTMimK1.js} +2 -2
- package/package.json +65 -55
- package/src/.prettierrc.json +16 -0
- package/src/App.vue +326 -269
- package/src/eslint.config.js +15 -0
- package/src/index.js +4 -0
- package/src/install.js +9 -10
- package/src/main.js +54 -57
- package/src/mixins/apple.mixin.js +56 -54
- package/src/mixins/auth.mixin.js +3 -2
- package/src/mixins/global.mixin.js +3 -3
- package/src/mixins/google.mixin.js +53 -54
- package/src/mixins/microsoft.mixin.js +2 -5
- package/src/mixins/okta.mixin.js +1 -1
- package/src/mixins/qr-auth.mixin.js +111 -107
- package/src/mixins/saml.mixin.js +82 -45
- package/src/router/index.js +6 -6
- package/src/routes.js +1 -1
- package/src/services/Api.js +56 -58
- package/src/services/AuthService.js +7 -9
- package/src/services/CompanyService.js +10 -10
- package/src/services/DeviceService.js +3 -3
- package/src/services/UserService.js +48 -45
- package/src/services/UtilService.js +317 -225
- package/src/store/auth.js +485 -549
- package/src/store/constants.js +2 -2
- package/src/store/event-bus.js +22 -22
- package/src/store/locales/cn.js +476 -458
- package/src/store/locales/de.js +478 -517
- package/src/store/locales/en.js +454 -513
- package/src/store/locales/es.js +477 -524
- package/src/store/locales/fr.js +477 -516
- package/src/store/locales/it.js +477 -514
- package/src/store/locales/ja.js +488 -0
- package/src/store/locales/kr.js +477 -491
- package/src/store/locales/lang.js +51 -43
- package/src/store/locales/pt.js +488 -0
- package/src/store/locales/sr.js +477 -492
- package/src/store/locales/tr.js +477 -487
- package/src/store/store.js +6 -6
- package/src/views/Account.vue +36 -8
- package/src/views/Billing.vue +464 -34
- package/src/views/Callback.vue +36 -33
- package/src/views/General.vue +151 -185
- package/src/views/Login.vue +2 -25
- package/src/views/Register.vue +2 -12
- package/src/views/Reset.vue +132 -135
- package/src/views/Security.vue +13 -7
- package/src/views/Verify.vue +153 -151
- package/src/views/Welcome.vue +85 -71
- package/dist/Account-Cuz87g_8.js +0 -153
- package/dist/Billing-BXlQEuNy.js +0 -113
- package/dist/CustomApp-CLCMXmMO.js +0 -83
- package/dist/General-dW73bMoR.js +0 -479
- package/dist/QR-D6ZGcPM0.js +0 -41
- package/dist/index.css +0 -193
- package/dist/web-AXRKjAOB.js +0 -92
- package/src/components/DELETE_Language.vue +0 -168
- package/src/components/DELETE_LinkIcon.vue +0 -288
- package/src/components/DELETE_ModalOverlay.vue +0 -68
- package/src/components/DELETE_OTP.vue +0 -105
- package/src/components/DELETE_SSO.vue +0 -120
- package/src/components/DELETE_SSOPick.vue +0 -166
- package/src/mixins/DELETE_mfa-auth.mixin.js +0 -53
- package/src/mixins/facebook.mixin.js +0 -78
package/src/store/store.js
CHANGED
package/src/views/Account.vue
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="page-login content-boxed content-boxed-padding center-text" style="margin-top: -1px; overflow: hidden; border: solid 0px #ffffff;">
|
|
3
3
|
<br>
|
|
4
4
|
<img v-if="false" :src="account.photo" class="user-photo margin-center" style="max-width: 110px;" alt="">
|
|
5
|
-
<h1 class="bold full-top no-bottom center-text">{{ssoLang[appLanguage].welcome_account}}</h1>
|
|
5
|
+
<h1 class="bold full-top no-bottom center-text text-[24px]">{{ssoLang[appLanguage].welcome_account}}</h1>
|
|
6
6
|
<p class="full-bottom half-top center-text font-16">{{account.email}}</p>
|
|
7
7
|
|
|
8
8
|
<div v-if="display !== 'npm' && false" class="full-top full-bottom">
|
|
@@ -79,7 +79,19 @@
|
|
|
79
79
|
</div>
|
|
80
80
|
</div>
|
|
81
81
|
|
|
82
|
-
<
|
|
82
|
+
<div v-if="isModal" class="settingRow" @click="$emit('update:viewProp', 'AuthGeneral')">
|
|
83
|
+
<div class="firstRow">
|
|
84
|
+
<img
|
|
85
|
+
:src="getIcon('user-black.svg')"
|
|
86
|
+
class="withBackground"
|
|
87
|
+
/>
|
|
88
|
+
<h4>{{ssoLang[appLanguage].general }}</h4>
|
|
89
|
+
|
|
90
|
+
<img :src="getIcon('arrow-gray-right.svg')" />
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<router-link v-else to="/general" class="settingRow">
|
|
83
95
|
<div class="firstRow">
|
|
84
96
|
<img
|
|
85
97
|
:src="getIcon('user-black.svg')"
|
|
@@ -91,7 +103,19 @@
|
|
|
91
103
|
</div>
|
|
92
104
|
</router-link>
|
|
93
105
|
|
|
94
|
-
<
|
|
106
|
+
<div v-if="isModal" class="settingRow" @click="$emit('update:viewProp', 'AuthSecurity')">
|
|
107
|
+
<div class="firstRow">
|
|
108
|
+
<img
|
|
109
|
+
:src="getIcon('shield-black.svg')"
|
|
110
|
+
class="withBackground"
|
|
111
|
+
/>
|
|
112
|
+
<h4>{{ssoLang[appLanguage].security }}</h4>
|
|
113
|
+
|
|
114
|
+
<img :src="getIcon('arrow-gray-right.svg')" />
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<router-link v-else to="/security" class="settingRow">
|
|
95
119
|
<div class="firstRow">
|
|
96
120
|
<img
|
|
97
121
|
:src="getIcon('shield-black.svg')"
|
|
@@ -103,7 +127,7 @@
|
|
|
103
127
|
</div>
|
|
104
128
|
</router-link>
|
|
105
129
|
|
|
106
|
-
<router-link to="/apps" class="settingRow">
|
|
130
|
+
<router-link v-if="false" to="/apps" class="settingRow">
|
|
107
131
|
<div class="firstRow">
|
|
108
132
|
<img
|
|
109
133
|
:src="getIcon('apps-black.svg')"
|
|
@@ -128,7 +152,7 @@
|
|
|
128
152
|
</router-link>
|
|
129
153
|
|
|
130
154
|
|
|
131
|
-
<div class="settingRow" @click="logoutAccount">
|
|
155
|
+
<div v-if="!isModal" class="settingRow" @click="logoutAccount">
|
|
132
156
|
<div class="firstRow">
|
|
133
157
|
<img
|
|
134
158
|
:src="getIcon('logout-red.svg')"
|
|
@@ -159,6 +183,12 @@ import { EventBus } from "../store/event-bus.js";
|
|
|
159
183
|
import CONSTANTS from "../store/constants.js";
|
|
160
184
|
export default {
|
|
161
185
|
mixins: [AuthMixin],
|
|
186
|
+
props: {
|
|
187
|
+
isModal: {
|
|
188
|
+
type: Boolean,
|
|
189
|
+
default: false
|
|
190
|
+
}
|
|
191
|
+
},
|
|
162
192
|
data () {
|
|
163
193
|
return {
|
|
164
194
|
expanded: false,
|
|
@@ -174,17 +204,15 @@ export default {
|
|
|
174
204
|
if (!this.isLoggedIn) {
|
|
175
205
|
return this.$router.push('/');
|
|
176
206
|
}
|
|
177
|
-
this.getAccountSettings();
|
|
207
|
+
await this.getAccountSettings();
|
|
178
208
|
},
|
|
179
209
|
methods: {
|
|
180
210
|
continueTo (realm) {
|
|
181
|
-
console.log('continue to ' + realm);
|
|
182
211
|
},
|
|
183
212
|
addAccount () {
|
|
184
213
|
this.$router.push('/login');
|
|
185
214
|
},
|
|
186
215
|
switchAccount (username) {
|
|
187
|
-
console.log('switch', username);
|
|
188
216
|
},
|
|
189
217
|
toggleLanguageModal () {
|
|
190
218
|
EventBus.$emit('toggleSSOLanguageModal')
|
package/src/views/Billing.vue
CHANGED
|
@@ -27,23 +27,98 @@
|
|
|
27
27
|
<p class="center-text">{{ssoLang[appLanguage].billing_p }}</p>
|
|
28
28
|
|
|
29
29
|
<div class="full-top">
|
|
30
|
-
|
|
30
|
+
<!-- No subscriptions message -->
|
|
31
|
+
<div v-if="subscriptions.length === 0" class="no-subscriptions center-text full-top">
|
|
32
|
+
<p class="gray-text">There are no active subscriptions at this point.</p>
|
|
33
|
+
</div>
|
|
31
34
|
|
|
32
|
-
|
|
35
|
+
<!-- Subscriptions list -->
|
|
36
|
+
<div v-else>
|
|
37
|
+
<div v-for="sub in subscriptions" :key="sub.subscriptionId" class="subscription-card half-bottom">
|
|
38
|
+
<div class="subscription-header">
|
|
39
|
+
<div class="subscription-info">
|
|
40
|
+
<h3 class="subscription-name">{{ sub.subscriptionName }}</h3>
|
|
41
|
+
<span class="subscription-status" :class="getStatusClass(sub.status)">
|
|
42
|
+
{{ getStatusText(sub.status) }}
|
|
43
|
+
</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
47
|
+
<div class="subscription-details">
|
|
48
|
+
<div class="detail-row">
|
|
49
|
+
<span class="detail-label">Amount:</span>
|
|
50
|
+
<span class="detail-value">{{ formatCurrency(sub.amount, sub.currency) }}</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="detail-row">
|
|
53
|
+
<span class="detail-label">Billing:</span>
|
|
54
|
+
<span class="detail-value">{{ formatInterval(sub.interval) }}</span>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="detail-row">
|
|
57
|
+
<span class="detail-label">Licenses:</span>
|
|
58
|
+
<span class="detail-value">{{ sub.licenses }}</span>
|
|
59
|
+
</div>
|
|
60
|
+
<div v-if="sub.isTrial" class="detail-row">
|
|
61
|
+
<span class="detail-label">Trial Ends:</span>
|
|
62
|
+
<span class="detail-value">{{ formatDate(sub.trialEnd) }}</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div v-else-if="sub.endDate" class="detail-row">
|
|
65
|
+
<span class="detail-label">Next Billing:</span>
|
|
66
|
+
<span class="detail-value">{{ formatDate(sub.endDate) }}</span>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
41
69
|
|
|
42
|
-
<
|
|
70
|
+
<div class="subscription-actions" v-if="sub.status !== 'canceled'">
|
|
71
|
+
<button
|
|
72
|
+
@click="openCancelModal(sub)"
|
|
73
|
+
class="cancel-button"
|
|
74
|
+
:disabled="loading"
|
|
75
|
+
>
|
|
76
|
+
{{ loading ? 'Processing...' : 'Cancel Subscription' }}
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
43
79
|
</div>
|
|
44
80
|
</div>
|
|
45
81
|
</div>
|
|
46
82
|
</div>
|
|
83
|
+
|
|
84
|
+
<!-- Cancel Confirmation Modal -->
|
|
85
|
+
<div v-if="showCancelModal" class="modal-overlay" @click.self="closeCancelModal">
|
|
86
|
+
<div class="modal-content">
|
|
87
|
+
<div class="modal-header">
|
|
88
|
+
<h3>Cancel Subscription</h3>
|
|
89
|
+
<button @click="closeCancelModal" class="close-button">×</button>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="modal-body">
|
|
93
|
+
<p class="modal-text">
|
|
94
|
+
Are you sure you want to cancel your subscription to <b>{{ selectedSubscription?.subscriptionName }}</b>?
|
|
95
|
+
</p>
|
|
96
|
+
|
|
97
|
+
<div class="feedback-section">
|
|
98
|
+
<label class="feedback-label">We'd love to know why you're canceling (optional):</label>
|
|
99
|
+
<textarea
|
|
100
|
+
v-model="cancelFeedback"
|
|
101
|
+
class="feedback-textarea"
|
|
102
|
+
placeholder="Your feedback helps us improve our service..."
|
|
103
|
+
rows="4"
|
|
104
|
+
></textarea>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="modal-footer">
|
|
109
|
+
<button @click="closeCancelModal" class="button-secondary">
|
|
110
|
+
Keep Subscription
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
@click="confirmCancel"
|
|
114
|
+
class="button-danger"
|
|
115
|
+
:disabled="loading"
|
|
116
|
+
>
|
|
117
|
+
{{ loading ? 'Canceling...' : 'Confirm Cancellation' }}
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
47
122
|
</div>
|
|
48
123
|
</template>
|
|
49
124
|
|
|
@@ -51,7 +126,7 @@
|
|
|
51
126
|
<script>
|
|
52
127
|
import AuthMixin from "../mixins/auth.mixin";
|
|
53
128
|
import {EventBus} from "@/store/event-bus.js";
|
|
54
|
-
import
|
|
129
|
+
import api from "@/services/Api.js";
|
|
55
130
|
|
|
56
131
|
export default {
|
|
57
132
|
name: "AuthBilling",
|
|
@@ -65,38 +140,132 @@ export default {
|
|
|
65
140
|
data () {
|
|
66
141
|
return {
|
|
67
142
|
loading: false,
|
|
68
|
-
subscriptions: []
|
|
143
|
+
subscriptions: [],
|
|
144
|
+
showCancelModal: false,
|
|
145
|
+
selectedSubscription: null,
|
|
146
|
+
cancelFeedback: ''
|
|
69
147
|
}
|
|
70
148
|
},
|
|
71
149
|
async mounted() {
|
|
72
150
|
if (!this.isLoggedIn) this.$router.push('/login');
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const response = await AuthService.getRecords({
|
|
76
|
-
objectId: '6dc545ea-2e2f-4720-b4fe-c5ebdd8af066',
|
|
77
|
-
query: {
|
|
78
|
-
where: {
|
|
79
|
-
account: this.account.id,
|
|
80
|
-
},
|
|
81
|
-
fields: ['t_subscriptions_name', 't_subscriptions_desc', 't_subscriptions_type', 't_subscriptions_active', 't_subscriptions_app'],
|
|
82
|
-
relations: {
|
|
83
|
-
t_subscriptions_app: {
|
|
84
|
-
fields: [
|
|
85
|
-
"t_apps_name",
|
|
86
|
-
"t_apps_icon",
|
|
87
|
-
"t_apps_url"
|
|
88
|
-
],
|
|
89
|
-
relations: {},
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
this.subscriptions = response.data.records;
|
|
151
|
+
await this.getAccountSettings();
|
|
152
|
+
this.loadSubscriptions();
|
|
95
153
|
},
|
|
96
154
|
methods: {
|
|
97
155
|
close () {
|
|
98
156
|
EventBus.$emit('ssoEvent', {name: 'toggleAuthModal', data: true})
|
|
99
157
|
},
|
|
158
|
+
loadSubscriptions() {
|
|
159
|
+
// Extract subscriptions from account.billing
|
|
160
|
+
if (this.account.billing) {
|
|
161
|
+
const billing = this.account.billing;
|
|
162
|
+
const subs = [];
|
|
163
|
+
|
|
164
|
+
// Loop through billing object to find subscription objects
|
|
165
|
+
for (const key in billing) {
|
|
166
|
+
if (key !== 'region' && key !== 'currency' && key !== 'paymentProfiles' && typeof billing[key] === 'object' && billing[key].subscriptionId) {
|
|
167
|
+
subs.push(billing[key]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
this.subscriptions = subs;
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
formatCurrency(amount, currency) {
|
|
175
|
+
const currencySymbols = {
|
|
176
|
+
'EUR': '€',
|
|
177
|
+
'USD': '$',
|
|
178
|
+
'GBP': '£'
|
|
179
|
+
};
|
|
180
|
+
const symbol = currencySymbols[currency?.toUpperCase()] || currency || '';
|
|
181
|
+
return `${symbol}${amount?.toFixed(2) || '0.00'}`;
|
|
182
|
+
},
|
|
183
|
+
formatInterval(interval) {
|
|
184
|
+
return interval ? `per ${interval}` : '';
|
|
185
|
+
},
|
|
186
|
+
formatDate(timestamp) {
|
|
187
|
+
if (!timestamp) return '';
|
|
188
|
+
const date = new Date(timestamp * 1000);
|
|
189
|
+
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' });
|
|
190
|
+
},
|
|
191
|
+
getStatusText(status) {
|
|
192
|
+
const statusMap = {
|
|
193
|
+
'trialing': 'Trial',
|
|
194
|
+
'active': 'Active',
|
|
195
|
+
'past_due': 'Past Due',
|
|
196
|
+
'canceled': 'Canceled',
|
|
197
|
+
'unpaid': 'Unpaid'
|
|
198
|
+
};
|
|
199
|
+
return statusMap[status] || status;
|
|
200
|
+
},
|
|
201
|
+
getStatusClass(status) {
|
|
202
|
+
return `status-${status}`;
|
|
203
|
+
},
|
|
204
|
+
openCancelModal(subscription) {
|
|
205
|
+
this.selectedSubscription = subscription;
|
|
206
|
+
this.showCancelModal = true;
|
|
207
|
+
this.cancelFeedback = '';
|
|
208
|
+
},
|
|
209
|
+
closeCancelModal() {
|
|
210
|
+
this.showCancelModal = false;
|
|
211
|
+
this.selectedSubscription = null;
|
|
212
|
+
this.cancelFeedback = '';
|
|
213
|
+
},
|
|
214
|
+
async confirmCancel() {
|
|
215
|
+
if (!this.selectedSubscription) return;
|
|
216
|
+
|
|
217
|
+
// Check if this is a RevenueCat in-app purchase subscription
|
|
218
|
+
if (this.selectedSubscription.paymentGateway === 'revenuecat') {
|
|
219
|
+
// Fire event to redirect user to RevenueCat management URL
|
|
220
|
+
EventBus.$emit('ssoEvent', {
|
|
221
|
+
name: 'subscriptionCancelled',
|
|
222
|
+
data: {
|
|
223
|
+
subscriptionId: this.selectedSubscription.subscriptionId,
|
|
224
|
+
paymentGateway: this.selectedSubscription.paymentGateway,
|
|
225
|
+
feedback: this.cancelFeedback
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Close the modal
|
|
230
|
+
this.closeCancelModal();
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// For other payment gateways (like Stripe), proceed with API cancellation
|
|
235
|
+
this.loading = true;
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
// Send cancel request to backend
|
|
239
|
+
const response = await api(false, 'v2').post('checkout/cancel/' + this.selectedSubscription.subscriptionId, {
|
|
240
|
+
feedback: this.cancelFeedback
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
if (response.data.success) {
|
|
244
|
+
// Show success message
|
|
245
|
+
EventBus.$emit('showToast', {
|
|
246
|
+
type: 'success',
|
|
247
|
+
message: 'Subscription canceled successfully'
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Refresh account settings to get updated billing info
|
|
251
|
+
await this.getAccountSettings();
|
|
252
|
+
this.loadSubscriptions();
|
|
253
|
+
|
|
254
|
+
// Close modal
|
|
255
|
+
this.closeCancelModal();
|
|
256
|
+
} else {
|
|
257
|
+
throw new Error(response.data.message || 'Failed to cancel subscription');
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
console.error('Error canceling subscription:', error);
|
|
261
|
+
EventBus.$emit('showToast', {
|
|
262
|
+
type: 'error',
|
|
263
|
+
message: error.response?.data?.message || 'Failed to cancel subscription. Please try again.'
|
|
264
|
+
});
|
|
265
|
+
} finally {
|
|
266
|
+
this.loading = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
100
269
|
}
|
|
101
270
|
};
|
|
102
271
|
</script>
|
|
@@ -105,4 +274,265 @@ export default {
|
|
|
105
274
|
.withBackground {
|
|
106
275
|
height: 50px;
|
|
107
276
|
}
|
|
277
|
+
|
|
278
|
+
.no-subscriptions {
|
|
279
|
+
padding: 40px 20px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.gray-text {
|
|
283
|
+
color: #666;
|
|
284
|
+
font-size: 16px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.subscription-card {
|
|
288
|
+
background: #fff;
|
|
289
|
+
border: 1px solid #e0e0e0;
|
|
290
|
+
border-radius: 12px;
|
|
291
|
+
padding: 20px;
|
|
292
|
+
margin-bottom: 16px;
|
|
293
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.subscription-header {
|
|
297
|
+
display: flex;
|
|
298
|
+
justify-content: space-between;
|
|
299
|
+
align-items: flex-start;
|
|
300
|
+
margin-bottom: 16px;
|
|
301
|
+
padding-bottom: 16px;
|
|
302
|
+
border-bottom: 1px solid #f0f0f0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.subscription-info {
|
|
306
|
+
flex: 1;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.subscription-name {
|
|
310
|
+
font-size: 20px;
|
|
311
|
+
font-weight: 600;
|
|
312
|
+
margin: 0 0 8px 0;
|
|
313
|
+
color: #000;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.subscription-status {
|
|
317
|
+
display: inline-block;
|
|
318
|
+
padding: 4px 12px;
|
|
319
|
+
border-radius: 12px;
|
|
320
|
+
font-size: 12px;
|
|
321
|
+
font-weight: 600;
|
|
322
|
+
text-transform: uppercase;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.status-trialing {
|
|
326
|
+
background: #e3f2fd;
|
|
327
|
+
color: #1976d2;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.status-active {
|
|
331
|
+
background: #e8f5e9;
|
|
332
|
+
color: #388e3c;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.status-past_due {
|
|
336
|
+
background: #fff3e0;
|
|
337
|
+
color: #f57c00;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.status-canceled {
|
|
341
|
+
background: #ffebee;
|
|
342
|
+
color: #d32f2f;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.subscription-details {
|
|
346
|
+
margin-bottom: 16px;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.detail-row {
|
|
350
|
+
display: flex;
|
|
351
|
+
justify-content: space-between;
|
|
352
|
+
padding: 8px 0;
|
|
353
|
+
font-size: 15px;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.detail-label {
|
|
357
|
+
color: #666;
|
|
358
|
+
font-weight: 500;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.detail-value {
|
|
362
|
+
color: #000;
|
|
363
|
+
font-weight: 600;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.subscription-actions {
|
|
367
|
+
display: flex;
|
|
368
|
+
justify-content: flex-end;
|
|
369
|
+
padding-top: 16px;
|
|
370
|
+
border-top: 1px solid #f0f0f0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.cancel-button {
|
|
374
|
+
padding: 10px 20px;
|
|
375
|
+
background: #fff;
|
|
376
|
+
color: #d32f2f;
|
|
377
|
+
border: 1px solid #d32f2f;
|
|
378
|
+
border-radius: 8px;
|
|
379
|
+
font-size: 14px;
|
|
380
|
+
font-weight: 600;
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
transition: all 0.2s;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.cancel-button:hover:not(:disabled) {
|
|
386
|
+
background: #d32f2f;
|
|
387
|
+
color: #fff;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.cancel-button:disabled {
|
|
391
|
+
opacity: 0.5;
|
|
392
|
+
cursor: not-allowed;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/* Modal Styles */
|
|
396
|
+
.modal-overlay {
|
|
397
|
+
position: fixed;
|
|
398
|
+
top: 0;
|
|
399
|
+
left: 0;
|
|
400
|
+
right: 0;
|
|
401
|
+
bottom: 0;
|
|
402
|
+
background: rgba(0, 0, 0, 0.5);
|
|
403
|
+
display: flex;
|
|
404
|
+
align-items: center;
|
|
405
|
+
justify-content: center;
|
|
406
|
+
z-index: 9999;
|
|
407
|
+
padding: 20px;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.modal-content {
|
|
411
|
+
background: #fff;
|
|
412
|
+
border-radius: 16px;
|
|
413
|
+
width: 100%;
|
|
414
|
+
max-width: 500px;
|
|
415
|
+
max-height: 90vh;
|
|
416
|
+
overflow-y: auto;
|
|
417
|
+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.modal-header {
|
|
421
|
+
display: flex;
|
|
422
|
+
justify-content: space-between;
|
|
423
|
+
align-items: center;
|
|
424
|
+
padding: 24px;
|
|
425
|
+
border-bottom: 1px solid #e0e0e0;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
.modal-header h3 {
|
|
429
|
+
margin: 0;
|
|
430
|
+
font-size: 20px;
|
|
431
|
+
font-weight: 600;
|
|
432
|
+
color: #000;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.close-button {
|
|
436
|
+
background: none;
|
|
437
|
+
border: none;
|
|
438
|
+
font-size: 28px;
|
|
439
|
+
color: #666;
|
|
440
|
+
cursor: pointer;
|
|
441
|
+
padding: 0;
|
|
442
|
+
width: 32px;
|
|
443
|
+
height: 32px;
|
|
444
|
+
display: flex;
|
|
445
|
+
align-items: center;
|
|
446
|
+
justify-content: center;
|
|
447
|
+
border-radius: 50%;
|
|
448
|
+
transition: background 0.2s;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
.close-button:hover {
|
|
452
|
+
background: #f0f0f0;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
.modal-body {
|
|
456
|
+
padding: 24px;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.modal-text {
|
|
460
|
+
font-size: 16px;
|
|
461
|
+
line-height: 1.5;
|
|
462
|
+
color: #333;
|
|
463
|
+
margin-bottom: 24px;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.feedback-section {
|
|
467
|
+
margin-top: 20px;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.feedback-label {
|
|
471
|
+
display: block;
|
|
472
|
+
font-size: 14px;
|
|
473
|
+
font-weight: 600;
|
|
474
|
+
color: #333;
|
|
475
|
+
margin-bottom: 8px;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
.feedback-textarea {
|
|
479
|
+
width: 100%;
|
|
480
|
+
padding: 12px;
|
|
481
|
+
border: 1px solid #e0e0e0;
|
|
482
|
+
border-radius: 8px;
|
|
483
|
+
font-size: 14px;
|
|
484
|
+
font-family: inherit;
|
|
485
|
+
resize: vertical;
|
|
486
|
+
transition: border-color 0.2s;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
.feedback-textarea:focus {
|
|
490
|
+
outline: none;
|
|
491
|
+
border-color: #1976d2;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
.modal-footer {
|
|
495
|
+
display: flex;
|
|
496
|
+
gap: 12px;
|
|
497
|
+
padding: 20px 24px;
|
|
498
|
+
border-top: 1px solid #e0e0e0;
|
|
499
|
+
justify-content: flex-end;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
.button-secondary {
|
|
503
|
+
padding: 12px 24px;
|
|
504
|
+
background: #fff;
|
|
505
|
+
color: #333;
|
|
506
|
+
border: 1px solid #e0e0e0;
|
|
507
|
+
border-radius: 8px;
|
|
508
|
+
font-size: 14px;
|
|
509
|
+
font-weight: 600;
|
|
510
|
+
cursor: pointer;
|
|
511
|
+
transition: all 0.2s;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.button-secondary:hover {
|
|
515
|
+
background: #f5f5f5;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.button-danger {
|
|
519
|
+
padding: 12px 24px;
|
|
520
|
+
background: #d32f2f;
|
|
521
|
+
color: #fff;
|
|
522
|
+
border: none;
|
|
523
|
+
border-radius: 8px;
|
|
524
|
+
font-size: 14px;
|
|
525
|
+
font-weight: 600;
|
|
526
|
+
cursor: pointer;
|
|
527
|
+
transition: all 0.2s;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
.button-danger:hover:not(:disabled) {
|
|
531
|
+
background: #b71c1c;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
.button-danger:disabled {
|
|
535
|
+
opacity: 0.5;
|
|
536
|
+
cursor: not-allowed;
|
|
537
|
+
}
|
|
108
538
|
</style>
|