@usequota/nextjs 0.1.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.
@@ -0,0 +1,439 @@
1
+ /**
2
+ * @usequota/nextjs - Base component styles
3
+ *
4
+ * Uses CSS custom properties for easy theming.
5
+ * Override these variables in your app to customize the look.
6
+ */
7
+
8
+ /* Theme variables with sensible defaults */
9
+ :root {
10
+ /* Primary palette - warm coral accent for a distinctive feel */
11
+ --quota-primary: #e85d4c;
12
+ --quota-primary-hover: #d14a3a;
13
+ --quota-primary-active: #bc3f30;
14
+
15
+ /* Neutral palette - warm grays for a softer appearance */
16
+ --quota-background: #faf9f7;
17
+ --quota-surface: #ffffff;
18
+ --quota-surface-elevated: #ffffff;
19
+ --quota-border: #e8e4df;
20
+ --quota-border-hover: #d4cfc8;
21
+
22
+ /* Text colors */
23
+ --quota-text: #2d2a26;
24
+ --quota-text-secondary: #6b6560;
25
+ --quota-text-muted: #9c9690;
26
+ --quota-text-on-primary: #ffffff;
27
+
28
+ /* State colors */
29
+ --quota-success: #3d9970;
30
+ --quota-warning: #e5a040;
31
+ --quota-error: #dc4a4a;
32
+
33
+ /* Shadows - subtle and warm */
34
+ --quota-shadow-sm: 0 1px 2px rgba(45, 42, 38, 0.05);
35
+ --quota-shadow-md: 0 4px 12px rgba(45, 42, 38, 0.08);
36
+ --quota-shadow-lg: 0 8px 24px rgba(45, 42, 38, 0.12);
37
+
38
+ /* Typography */
39
+ --quota-font-family:
40
+ "DM Sans", "Outfit", system-ui, -apple-system, sans-serif;
41
+ --quota-font-mono: "JetBrains Mono", "Fira Code", ui-monospace, monospace;
42
+
43
+ /* Spacing and sizing */
44
+ --quota-radius-sm: 6px;
45
+ --quota-radius-md: 10px;
46
+ --quota-radius-lg: 14px;
47
+ --quota-radius-full: 9999px;
48
+
49
+ /* Transitions */
50
+ --quota-transition-fast: 120ms cubic-bezier(0.25, 0.1, 0.25, 1);
51
+ --quota-transition-normal: 200ms cubic-bezier(0.25, 0.1, 0.25, 1);
52
+ --quota-transition-slow: 320ms cubic-bezier(0.25, 0.1, 0.25, 1);
53
+ }
54
+
55
+ /* Dark mode variables */
56
+ @media (prefers-color-scheme: dark) {
57
+ :root {
58
+ --quota-primary: #f07563;
59
+ --quota-primary-hover: #ff8a7a;
60
+ --quota-primary-active: #e85d4c;
61
+
62
+ --quota-background: #1a1816;
63
+ --quota-surface: #242120;
64
+ --quota-surface-elevated: #2e2a28;
65
+ --quota-border: #3d3836;
66
+ --quota-border-hover: #524b48;
67
+
68
+ --quota-text: #f5f3f0;
69
+ --quota-text-secondary: #b8b3ad;
70
+ --quota-text-muted: #7a7570;
71
+ --quota-text-on-primary: #1a1816;
72
+
73
+ --quota-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
74
+ --quota-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.3);
75
+ --quota-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.4);
76
+ }
77
+ }
78
+
79
+ /* Base button styles */
80
+ .quota-button {
81
+ font-family: var(--quota-font-family);
82
+ font-size: 14px;
83
+ font-weight: 500;
84
+ line-height: 1.4;
85
+ padding: 10px 18px;
86
+ border-radius: var(--quota-radius-md);
87
+ border: 1px solid transparent;
88
+ cursor: pointer;
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ gap: 8px;
93
+ text-decoration: none;
94
+ white-space: nowrap;
95
+ transition:
96
+ background-color var(--quota-transition-fast),
97
+ border-color var(--quota-transition-fast),
98
+ box-shadow var(--quota-transition-fast),
99
+ transform var(--quota-transition-fast);
100
+ position: relative;
101
+ overflow: hidden;
102
+ }
103
+
104
+ .quota-button:focus-visible {
105
+ outline: 2px solid var(--quota-primary);
106
+ outline-offset: 2px;
107
+ }
108
+
109
+ .quota-button:disabled {
110
+ opacity: 0.5;
111
+ cursor: not-allowed;
112
+ transform: none;
113
+ }
114
+
115
+ /* Primary button variant */
116
+ .quota-button--primary {
117
+ background-color: var(--quota-primary);
118
+ color: var(--quota-text-on-primary);
119
+ border-color: var(--quota-primary);
120
+ box-shadow: var(--quota-shadow-sm);
121
+ }
122
+
123
+ .quota-button--primary:hover:not(:disabled) {
124
+ background-color: var(--quota-primary-hover);
125
+ border-color: var(--quota-primary-hover);
126
+ box-shadow: var(--quota-shadow-md);
127
+ transform: translateY(-1px);
128
+ }
129
+
130
+ .quota-button--primary:active:not(:disabled) {
131
+ background-color: var(--quota-primary-active);
132
+ border-color: var(--quota-primary-active);
133
+ box-shadow: var(--quota-shadow-sm);
134
+ transform: translateY(0);
135
+ }
136
+
137
+ /* Secondary button variant */
138
+ .quota-button--secondary {
139
+ background-color: var(--quota-surface);
140
+ color: var(--quota-text);
141
+ border-color: var(--quota-border);
142
+ box-shadow: var(--quota-shadow-sm);
143
+ }
144
+
145
+ .quota-button--secondary:hover:not(:disabled) {
146
+ background-color: var(--quota-surface-elevated);
147
+ border-color: var(--quota-border-hover);
148
+ box-shadow: var(--quota-shadow-md);
149
+ transform: translateY(-1px);
150
+ }
151
+
152
+ .quota-button--secondary:active:not(:disabled) {
153
+ background-color: var(--quota-surface);
154
+ border-color: var(--quota-border);
155
+ box-shadow: var(--quota-shadow-sm);
156
+ transform: translateY(0);
157
+ }
158
+
159
+ /* Ghost button variant */
160
+ .quota-button--ghost {
161
+ background-color: transparent;
162
+ color: var(--quota-text);
163
+ border-color: transparent;
164
+ }
165
+
166
+ .quota-button--ghost:hover:not(:disabled) {
167
+ background-color: var(--quota-border);
168
+ }
169
+
170
+ .quota-button--ghost:active:not(:disabled) {
171
+ background-color: var(--quota-border-hover);
172
+ }
173
+
174
+ /* Loading spinner animation */
175
+ @keyframes quota-spin {
176
+ from {
177
+ transform: rotate(0deg);
178
+ }
179
+ to {
180
+ transform: rotate(360deg);
181
+ }
182
+ }
183
+
184
+ .quota-spinner {
185
+ width: 16px;
186
+ height: 16px;
187
+ border: 2px solid currentColor;
188
+ border-top-color: transparent;
189
+ border-radius: 50%;
190
+ animation: quota-spin 0.8s linear infinite;
191
+ }
192
+
193
+ /* Balance display */
194
+ .quota-balance {
195
+ font-family: var(--quota-font-family);
196
+ display: inline-flex;
197
+ align-items: center;
198
+ gap: 6px;
199
+ padding: 6px 12px;
200
+ background-color: var(--quota-surface);
201
+ border: 1px solid var(--quota-border);
202
+ border-radius: var(--quota-radius-md);
203
+ font-size: 14px;
204
+ font-weight: 500;
205
+ color: var(--quota-text);
206
+ box-shadow: var(--quota-shadow-sm);
207
+ }
208
+
209
+ .quota-balance__icon {
210
+ width: 16px;
211
+ height: 16px;
212
+ color: var(--quota-primary);
213
+ flex-shrink: 0;
214
+ }
215
+
216
+ .quota-balance__value {
217
+ font-variant-numeric: tabular-nums;
218
+ font-family: var(--quota-font-mono);
219
+ font-size: 13px;
220
+ letter-spacing: -0.02em;
221
+ }
222
+
223
+ .quota-balance--loading .quota-balance__value {
224
+ color: var(--quota-text-muted);
225
+ }
226
+
227
+ .quota-balance__error {
228
+ color: var(--quota-error);
229
+ font-size: 12px;
230
+ }
231
+
232
+ /* User menu dropdown */
233
+ .quota-user-menu {
234
+ position: relative;
235
+ display: inline-block;
236
+ font-family: var(--quota-font-family);
237
+ }
238
+
239
+ .quota-user-menu__trigger {
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: 10px;
243
+ padding: 6px 12px;
244
+ background-color: var(--quota-surface);
245
+ border: 1px solid var(--quota-border);
246
+ border-radius: var(--quota-radius-full);
247
+ cursor: pointer;
248
+ font-size: 14px;
249
+ color: var(--quota-text);
250
+ transition:
251
+ border-color var(--quota-transition-fast),
252
+ box-shadow var(--quota-transition-fast);
253
+ box-shadow: var(--quota-shadow-sm);
254
+ }
255
+
256
+ .quota-user-menu__trigger:hover {
257
+ border-color: var(--quota-border-hover);
258
+ box-shadow: var(--quota-shadow-md);
259
+ }
260
+
261
+ .quota-user-menu__trigger:focus-visible {
262
+ outline: 2px solid var(--quota-primary);
263
+ outline-offset: 2px;
264
+ }
265
+
266
+ .quota-user-menu__avatar {
267
+ width: 28px;
268
+ height: 28px;
269
+ border-radius: 50%;
270
+ background: linear-gradient(135deg, var(--quota-primary) 0%, #b44d3f 100%);
271
+ color: var(--quota-text-on-primary);
272
+ display: flex;
273
+ align-items: center;
274
+ justify-content: center;
275
+ font-size: 12px;
276
+ font-weight: 600;
277
+ text-transform: uppercase;
278
+ flex-shrink: 0;
279
+ }
280
+
281
+ .quota-user-menu__email {
282
+ max-width: 160px;
283
+ overflow: hidden;
284
+ text-overflow: ellipsis;
285
+ white-space: nowrap;
286
+ font-weight: 500;
287
+ }
288
+
289
+ .quota-user-menu__chevron {
290
+ width: 16px;
291
+ height: 16px;
292
+ color: var(--quota-text-muted);
293
+ transition: transform var(--quota-transition-fast);
294
+ flex-shrink: 0;
295
+ }
296
+
297
+ .quota-user-menu__trigger[aria-expanded="true"] .quota-user-menu__chevron {
298
+ transform: rotate(180deg);
299
+ }
300
+
301
+ .quota-user-menu__dropdown {
302
+ position: absolute;
303
+ top: calc(100% + 6px);
304
+ right: 0;
305
+ min-width: 220px;
306
+ background-color: var(--quota-surface);
307
+ border: 1px solid var(--quota-border);
308
+ border-radius: var(--quota-radius-lg);
309
+ box-shadow: var(--quota-shadow-lg);
310
+ padding: 6px;
311
+ z-index: 1000;
312
+ opacity: 0;
313
+ visibility: hidden;
314
+ transform: translateY(-8px) scale(0.96);
315
+ transform-origin: top right;
316
+ transition:
317
+ opacity var(--quota-transition-normal),
318
+ visibility var(--quota-transition-normal),
319
+ transform var(--quota-transition-normal);
320
+ }
321
+
322
+ .quota-user-menu__dropdown--open {
323
+ opacity: 1;
324
+ visibility: visible;
325
+ transform: translateY(0) scale(1);
326
+ }
327
+
328
+ .quota-user-menu__section {
329
+ padding: 10px 12px;
330
+ border-bottom: 1px solid var(--quota-border);
331
+ }
332
+
333
+ .quota-user-menu__section:last-child {
334
+ border-bottom: none;
335
+ }
336
+
337
+ .quota-user-menu__label {
338
+ font-size: 11px;
339
+ font-weight: 600;
340
+ text-transform: uppercase;
341
+ letter-spacing: 0.05em;
342
+ color: var(--quota-text-muted);
343
+ margin-bottom: 4px;
344
+ }
345
+
346
+ .quota-user-menu__balance-value {
347
+ font-family: var(--quota-font-mono);
348
+ font-size: 18px;
349
+ font-weight: 600;
350
+ color: var(--quota-text);
351
+ letter-spacing: -0.02em;
352
+ }
353
+
354
+ .quota-user-menu__balance-dollars {
355
+ font-size: 12px;
356
+ color: var(--quota-text-secondary);
357
+ margin-top: 2px;
358
+ }
359
+
360
+ .quota-user-menu__item {
361
+ display: flex;
362
+ align-items: center;
363
+ gap: 10px;
364
+ width: 100%;
365
+ padding: 10px 12px;
366
+ margin: 2px 0;
367
+ background: none;
368
+ border: none;
369
+ border-radius: var(--quota-radius-sm);
370
+ font-family: var(--quota-font-family);
371
+ font-size: 14px;
372
+ font-weight: 500;
373
+ color: var(--quota-text);
374
+ cursor: pointer;
375
+ text-align: left;
376
+ transition: background-color var(--quota-transition-fast);
377
+ }
378
+
379
+ .quota-user-menu__item:hover {
380
+ background-color: var(--quota-border);
381
+ }
382
+
383
+ .quota-user-menu__item:focus-visible {
384
+ outline: 2px solid var(--quota-primary);
385
+ outline-offset: -2px;
386
+ }
387
+
388
+ .quota-user-menu__item--danger {
389
+ color: var(--quota-error);
390
+ }
391
+
392
+ .quota-user-menu__item--danger:hover {
393
+ background-color: rgba(220, 74, 74, 0.1);
394
+ }
395
+
396
+ .quota-user-menu__item-icon {
397
+ width: 18px;
398
+ height: 18px;
399
+ flex-shrink: 0;
400
+ }
401
+
402
+ /* Pulse animation for loading states */
403
+ @keyframes quota-pulse {
404
+ 0%,
405
+ 100% {
406
+ opacity: 1;
407
+ }
408
+ 50% {
409
+ opacity: 0.5;
410
+ }
411
+ }
412
+
413
+ .quota-loading-pulse {
414
+ animation: quota-pulse 1.5s ease-in-out infinite;
415
+ }
416
+
417
+ /* Screen reader only utility */
418
+ .quota-sr-only {
419
+ position: absolute;
420
+ width: 1px;
421
+ height: 1px;
422
+ padding: 0;
423
+ margin: -1px;
424
+ overflow: hidden;
425
+ clip: rect(0, 0, 0, 0);
426
+ white-space: nowrap;
427
+ border: 0;
428
+ }
429
+
430
+ /* Respect reduced motion preference */
431
+ @media (prefers-reduced-motion: reduce) {
432
+ *,
433
+ *::before,
434
+ *::after {
435
+ animation-duration: 0.01ms !important;
436
+ animation-iteration-count: 1 !important;
437
+ transition-duration: 0.01ms !important;
438
+ }
439
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@usequota/nextjs",
3
+ "version": "0.1.0",
4
+ "description": "Next.js SDK for Quota — AI credit billing middleware, hooks, and components",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/craigdossantos/quota",
9
+ "directory": "packages/quota-nextjs"
10
+ },
11
+ "homepage": "https://usequota.app/docs/nextjs-sdk",
12
+ "keywords": [
13
+ "quota",
14
+ "ai",
15
+ "billing",
16
+ "credits",
17
+ "nextjs",
18
+ "react",
19
+ "middleware",
20
+ "oauth"
21
+ ],
22
+ "main": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.mjs",
28
+ "require": "./dist/index.js"
29
+ },
30
+ "./server": {
31
+ "types": "./dist/server.d.ts",
32
+ "import": "./dist/server.mjs",
33
+ "require": "./dist/server.js"
34
+ },
35
+ "./styles.css": "./dist/styles.css"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "files": [
41
+ "dist"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsup src/index.ts src/server.ts --format cjs,esm --dts && cp src/components/styles.css dist/styles.css",
45
+ "test": "vitest",
46
+ "test:run": "vitest run"
47
+ },
48
+ "peerDependencies": {
49
+ "next": ">=13.0.0",
50
+ "react": ">=18.0.0"
51
+ },
52
+ "dependencies": {
53
+ "@usequota/types": "^0.1.0"
54
+ },
55
+ "devDependencies": {
56
+ "@testing-library/jest-dom": "^6.6.0",
57
+ "@testing-library/react": "^16.0.0",
58
+ "@testing-library/user-event": "^14.6.1",
59
+ "@types/react": "^18.0.0",
60
+ "@vitejs/plugin-react": "^4.3.0",
61
+ "jsdom": "^25.0.0",
62
+ "next": "^14.0.0",
63
+ "react": "^18.0.0",
64
+ "react-dom": "^18.0.0",
65
+ "tsup": "^8.0.0",
66
+ "typescript": "^5.7.0",
67
+ "vitest": "^2.1.8"
68
+ }
69
+ }