@contentgrowth/content-auth 0.4.9 → 0.5.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.
@@ -0,0 +1,115 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue';
3
+ import { createClient } from '../../clients/vue-client';
4
+
5
+ interface Props {
6
+ baseUrl?: string;
7
+ className?: string;
8
+ }
9
+
10
+ const props = withDefaults(defineProps<Props>(), {
11
+ className: '',
12
+ });
13
+
14
+ const emit = defineEmits<{
15
+ (e: 'success', data?: any): void;
16
+ (e: 'error', error: string): void;
17
+ }>();
18
+
19
+ const client = computed(() => createClient(props.baseUrl));
20
+
21
+ const currentPassword = ref('');
22
+ const newPassword = ref('');
23
+ const confirmPassword = ref('');
24
+ const loading = ref(false);
25
+ const error = ref<string | null>(null);
26
+ const success = ref(false);
27
+
28
+ const handleSubmit = async () => {
29
+ if (newPassword.value !== confirmPassword.value) {
30
+ error.value = "New passwords don't match";
31
+ emit('error', error.value);
32
+ return;
33
+ }
34
+
35
+ loading.value = true;
36
+ error.value = null;
37
+ success.value = false;
38
+
39
+ try {
40
+ const res = await client.value.changePassword({
41
+ currentPassword: currentPassword.value,
42
+ newPassword: newPassword.value
43
+ });
44
+
45
+ if (res?.error) {
46
+ throw new Error(res.error.message);
47
+ }
48
+
49
+ // Clear form on success
50
+ currentPassword.value = '';
51
+ newPassword.value = '';
52
+ confirmPassword.value = '';
53
+ success.value = true;
54
+
55
+ emit('success', res?.data);
56
+ } catch (err: any) {
57
+ if (err?.code === 'CREDENTIAL_ACCOUNT_NOT_FOUND' || err?.message?.includes('Credential account not found')) {
58
+ error.value = "You are logged in via a social provider (e.g. GitHub, Google) and do not have a password set.";
59
+ } else {
60
+ error.value = err.message || 'Failed to change password';
61
+ }
62
+ emit('error', error.value);
63
+ } finally {
64
+ loading.value = false;
65
+ }
66
+ };
67
+ </script>
68
+
69
+ <template>
70
+ <form :class="`ca-form ${className}`" @submit.prevent="handleSubmit">
71
+ <div class="ca-input-group">
72
+ <label class="ca-label" for="current-password">Current Password</label>
73
+ <input
74
+ id="current-password"
75
+ v-model="currentPassword"
76
+ type="password"
77
+ class="ca-input"
78
+ required
79
+ />
80
+ </div>
81
+
82
+ <div class="ca-input-group">
83
+ <label class="ca-label" for="new-password">New Password</label>
84
+ <input
85
+ id="new-password"
86
+ v-model="newPassword"
87
+ type="password"
88
+ class="ca-input"
89
+ minlength="8"
90
+ required
91
+ />
92
+ </div>
93
+
94
+ <div class="ca-input-group">
95
+ <label class="ca-label" for="confirm-password">Confirm New Password</label>
96
+ <input
97
+ id="confirm-password"
98
+ v-model="confirmPassword"
99
+ type="password"
100
+ class="ca-input"
101
+ minlength="8"
102
+ required
103
+ />
104
+ </div>
105
+
106
+ <div v-if="error" class="ca-error">{{ error }}</div>
107
+ <div v-if="success" class="ca-success-message" style="padding: 0.75rem; background: #d1fae5; border-radius: 6px; color: #065f46; text-align: center;">
108
+ Password updated successfully!
109
+ </div>
110
+
111
+ <button type="submit" class="ca-button" :disabled="loading">
112
+ {{ loading ? 'Updating...' : 'Update Password' }}
113
+ </button>
114
+ </form>
115
+ </template>
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed, watch } from 'vue';
3
+ import { createClient } from '../../clients/vue-client';
4
+
5
+ interface Props {
6
+ baseUrl?: string;
7
+ defaultName?: string;
8
+ defaultImage?: string;
9
+ className?: string;
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ defaultName: '',
14
+ defaultImage: '',
15
+ className: '',
16
+ });
17
+
18
+ const emit = defineEmits<{
19
+ (e: 'success', data?: any): void;
20
+ (e: 'error', error: string): void;
21
+ }>();
22
+
23
+ const client = computed(() => createClient(props.baseUrl));
24
+
25
+ const name = ref(props.defaultName);
26
+ const image = ref(props.defaultImage);
27
+ const loading = ref(false);
28
+ const error = ref<string | null>(null);
29
+ const success = ref(false);
30
+ const imageError = ref(false);
31
+
32
+ watch(() => props.defaultName, (val) => { if (val) name.value = val; });
33
+ watch(() => props.defaultImage, (val) => { if (val) image.value = val; });
34
+
35
+ const handleSubmit = async () => {
36
+ loading.value = true;
37
+ error.value = null;
38
+ success.value = false;
39
+
40
+ try {
41
+ await client.value.updateUser({
42
+ name: name.value,
43
+ image: image.value
44
+ });
45
+ success.value = true;
46
+ emit('success');
47
+ } catch (err: any) {
48
+ error.value = err.message || 'Failed to update profile';
49
+ emit('error', error.value);
50
+ } finally {
51
+ loading.value = false;
52
+ }
53
+ };
54
+
55
+ const handleImageError = () => {
56
+ imageError.value = true;
57
+ };
58
+
59
+ const handleImageInput = () => {
60
+ imageError.value = false;
61
+ };
62
+ </script>
63
+
64
+ <template>
65
+ <form :class="`ca-form ${className}`" @submit.prevent="handleSubmit">
66
+ <div class="ca-input-group">
67
+ <label class="ca-label" for="profile-name">Name</label>
68
+ <input
69
+ id="profile-name"
70
+ v-model="name"
71
+ type="text"
72
+ class="ca-input"
73
+ placeholder="Your Name"
74
+ />
75
+ </div>
76
+
77
+ <div class="ca-input-group">
78
+ <label class="ca-label" for="profile-image">Avatar URL</label>
79
+ <div style="display: flex; gap: 10px; align-items: center;">
80
+ <input
81
+ id="profile-image"
82
+ v-model="image"
83
+ type="url"
84
+ class="ca-input"
85
+ placeholder="https://example.com/avatar.jpg"
86
+ style="flex: 1;"
87
+ @input="handleImageInput"
88
+ />
89
+ <div
90
+ v-if="image && !imageError"
91
+ style="width: 40px; height: 40px; border-radius: 50%; overflow: hidden; flex-shrink: 0; border: 1px solid #eee;"
92
+ >
93
+ <img
94
+ :src="image"
95
+ alt="Preview"
96
+ style="width: 100%; height: 100%; object-fit: cover;"
97
+ @error="handleImageError"
98
+ />
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <div v-if="error" class="ca-error">{{ error }}</div>
104
+ <div v-if="success" class="ca-success-message" style="padding: 0.75rem; background: #d1fae5; border-radius: 6px; color: #065f46; text-align: center;">
105
+ Profile updated successfully!
106
+ </div>
107
+
108
+ <button type="submit" class="ca-button" :disabled="loading">
109
+ {{ loading ? 'Saving...' : 'Save Profile' }}
110
+ </button>
111
+ </form>
112
+ </template>
@@ -0,0 +1,150 @@
1
+ <script setup lang="ts">
2
+ import { ref, computed } from 'vue';
3
+ import { createClient } from '../../clients/vue-client';
4
+
5
+ interface Props {
6
+ token?: string | null;
7
+ baseUrl?: string;
8
+ backToLoginUrl?: string;
9
+ title?: string;
10
+ width?: 'default' | 'compact' | 'wide';
11
+ }
12
+
13
+ const props = withDefaults(defineProps<Props>(), {
14
+ title: 'Set New Password',
15
+ width: 'default',
16
+ });
17
+
18
+ const emit = defineEmits<{
19
+ (e: 'success'): void;
20
+ (e: 'error', error: string): void;
21
+ }>();
22
+
23
+ const client = computed(() => createClient(props.baseUrl));
24
+
25
+ const password = ref('');
26
+ const confirmPassword = ref('');
27
+ const loading = ref(false);
28
+ const error = ref<string | null>(null);
29
+ const success = ref(false);
30
+
31
+ const widthClass = computed(() => {
32
+ if (props.width === 'compact') return 'ca-width-compact';
33
+ if (props.width === 'wide') return 'ca-width-wide';
34
+ return 'ca-width-default';
35
+ });
36
+
37
+ const hasToken = computed(() => !!props.token);
38
+
39
+ const handleSubmit = async () => {
40
+ if (password.value !== confirmPassword.value) {
41
+ error.value = 'Passwords do not match';
42
+ return;
43
+ }
44
+
45
+ if (password.value.length < 8) {
46
+ error.value = 'Password must be at least 8 characters';
47
+ return;
48
+ }
49
+
50
+ loading.value = true;
51
+ error.value = null;
52
+
53
+ try {
54
+ const { error: err } = await client.value.resetPassword({
55
+ token: props.token!,
56
+ newPassword: password.value
57
+ });
58
+ if (err) throw err;
59
+ success.value = true;
60
+ emit('success');
61
+ } catch (err: any) {
62
+ error.value = err.message || 'Failed to reset password';
63
+ emit('error', error.value);
64
+ } finally {
65
+ loading.value = false;
66
+ }
67
+ };
68
+ </script>
69
+
70
+ <template>
71
+ <div :class="`ca-container ${widthClass}`">
72
+ <h2 class="ca-title">{{ title }}</h2>
73
+
74
+ <!-- Missing Token State -->
75
+ <template v-if="!hasToken">
76
+ <div class="ca-error-message">
77
+ <svg class="ca-error-icon" viewBox="0 0 24 24" width="48" height="48">
78
+ <circle cx="12" cy="12" r="10" fill="#EF4444" />
79
+ <path d="M12 8v4M12 16h.01" stroke="white" stroke-width="2" stroke-linecap="round" />
80
+ </svg>
81
+ <h3 class="ca-error-title">Invalid or Missing Token</h3>
82
+ <p class="ca-error-text">
83
+ The password reset link is invalid or has expired.
84
+ Please request a new password reset.
85
+ </p>
86
+ </div>
87
+ <div v-if="backToLoginUrl" class="ca-footer">
88
+ <a :href="backToLoginUrl" class="ca-link">Back to Sign In</a>
89
+ </div>
90
+ </template>
91
+
92
+ <!-- Success State -->
93
+ <template v-else-if="success">
94
+ <div class="ca-success-message">
95
+ <svg class="ca-success-icon" viewBox="0 0 24 24" width="48" height="48">
96
+ <circle cx="12" cy="12" r="10" fill="#10B981" />
97
+ <path d="M8 12l2.5 2.5L16 9" stroke="white" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round" />
98
+ </svg>
99
+ <h3 class="ca-success-title">Password Reset Successful</h3>
100
+ <p class="ca-success-text">
101
+ Your password has been successfully reset. You can now sign in with your new password.
102
+ </p>
103
+ </div>
104
+ <div v-if="backToLoginUrl" class="ca-footer">
105
+ <a :href="backToLoginUrl" class="ca-link">Sign In</a>
106
+ </div>
107
+ </template>
108
+
109
+ <!-- Form State -->
110
+ <template v-else>
111
+ <p class="ca-subtitle">Enter your new password below.</p>
112
+ <form class="ca-form" @submit.prevent="handleSubmit">
113
+ <div class="ca-input-group">
114
+ <label class="ca-label" for="password">New Password</label>
115
+ <input
116
+ id="password"
117
+ v-model="password"
118
+ type="password"
119
+ class="ca-input"
120
+ placeholder="At least 8 characters"
121
+ minlength="8"
122
+ required
123
+ />
124
+ </div>
125
+
126
+ <div class="ca-input-group">
127
+ <label class="ca-label" for="confirmPassword">Confirm Password</label>
128
+ <input
129
+ id="confirmPassword"
130
+ v-model="confirmPassword"
131
+ type="password"
132
+ class="ca-input"
133
+ placeholder="Confirm your password"
134
+ required
135
+ />
136
+ </div>
137
+
138
+ <div v-if="error" class="ca-error">{{ error }}</div>
139
+
140
+ <button type="submit" class="ca-button" :disabled="loading">
141
+ {{ loading ? 'Resetting...' : 'Reset Password' }}
142
+ </button>
143
+ </form>
144
+
145
+ <div v-if="backToLoginUrl" class="ca-footer">
146
+ <a :href="backToLoginUrl" class="ca-link">Back to Sign In</a>
147
+ </div>
148
+ </template>
149
+ </div>
150
+ </template>
@@ -7,7 +7,7 @@ import {
7
7
  PasswordChanger,
8
8
  ProfileEditor,
9
9
  ResetPasswordForm
10
- } from "../chunk-CTASTCWI.js";
10
+ } from "../chunk-3HNFZJ7S.js";
11
11
  import {
12
12
  authClient,
13
13
  createClient
@@ -0,0 +1,12 @@
1
+ export { default as AuthForm } from './components/vue/AuthForm.vue';
2
+ export { default as ForgotPasswordForm } from './components/vue/ForgotPasswordForm.vue';
3
+ export { default as ResetPasswordForm } from './components/vue/ResetPasswordForm.vue';
4
+ export { default as PasswordChanger } from './components/vue/PasswordChanger.vue';
5
+ export { default as ProfileEditor } from './components/vue/ProfileEditor.vue';
6
+ export { default as Organization } from './components/vue/Organization.vue';
7
+ export { authClient, createClient } from './clients/vue-client.js';
8
+ import 'nanostores';
9
+ import 'vue';
10
+ import 'better-auth';
11
+ import '@better-fetch/fetch';
12
+ import 'better-auth/plugins';
@@ -0,0 +1,23 @@
1
+ import {
2
+ authClient,
3
+ createClient
4
+ } from "../chunk-F2G7XJIZ.js";
5
+ import "../chunk-R5U7XKVJ.js";
6
+
7
+ // src/frontend/vue.ts
8
+ import { default as default2 } from "./components/vue/AuthForm.vue";
9
+ import { default as default3 } from "./components/vue/ForgotPasswordForm.vue";
10
+ import { default as default4 } from "./components/vue/ResetPasswordForm.vue";
11
+ import { default as default5 } from "./components/vue/PasswordChanger.vue";
12
+ import { default as default6 } from "./components/vue/ProfileEditor.vue";
13
+ import { default as default7 } from "./components/vue/Organization.vue";
14
+ export {
15
+ default2 as AuthForm,
16
+ default3 as ForgotPasswordForm,
17
+ default7 as Organization,
18
+ default5 as PasswordChanger,
19
+ default6 as ProfileEditor,
20
+ default4 as ResetPasswordForm,
21
+ authClient,
22
+ createClient
23
+ };
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  PasswordChanger,
20
20
  ProfileEditor,
21
21
  ResetPasswordForm
22
- } from "./chunk-CTASTCWI.js";
22
+ } from "./chunk-3HNFZJ7S.js";
23
23
  import {
24
24
  authClient,
25
25
  createClient
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentgrowth/content-auth",
3
- "version": "0.4.9",
3
+ "version": "0.5.1",
4
4
  "description": "Better Auth wrapper with UI components for Cloudflare Workers & Pages. Includes custom schema mapping, Turnstile bot protection, and email normalization.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -26,6 +26,24 @@
26
26
  "types": "./dist/frontend/client.d.ts",
27
27
  "import": "./dist/frontend/client.js"
28
28
  },
29
+ "./astro": {
30
+ "types": "./dist/frontend/astro.d.ts",
31
+ "import": "./dist/frontend/astro.js"
32
+ },
33
+ "./astro/client": {
34
+ "types": "./dist/frontend/clients/astro-client.d.ts",
35
+ "import": "./dist/frontend/clients/astro-client.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./dist/frontend/vue.d.ts",
39
+ "import": "./dist/frontend/vue.js"
40
+ },
41
+ "./vue/client": {
42
+ "types": "./dist/frontend/clients/vue-client.d.ts",
43
+ "import": "./dist/frontend/clients/vue-client.js"
44
+ },
45
+ "./astro/components/*": "./dist/frontend/components/astro/*",
46
+ "./vue/components/*": "./dist/frontend/components/vue/*",
29
47
  "./styles.css": "./dist/styles.css"
30
48
  },
31
49
  "files": [
@@ -35,7 +53,7 @@
35
53
  "LICENSE"
36
54
  ],
37
55
  "scripts": {
38
- "build": "tsup && cp src/styles.css dist/styles.css",
56
+ "build": "tsup && cp src/styles.css dist/styles.css && mkdir -p dist/frontend/components && cp -r src/frontend/components/vue dist/frontend/components/ && cp -r src/frontend/components/astro dist/frontend/components/",
39
57
  "dev": "tsup --watch",
40
58
  "prepublishOnly": "npm run build"
41
59
  },
@@ -45,14 +63,17 @@
45
63
  "cloudflare",
46
64
  "workers",
47
65
  "pages",
48
- "react"
66
+ "react",
67
+ "astro",
68
+ "vue"
49
69
  ],
50
70
  "author": "Content Growth",
51
71
  "license": "MIT",
52
72
  "peerDependencies": {
53
73
  "@marsidev/react-turnstile": ">=0.5.0",
54
74
  "react": "^18.0.0 || ^19.0.0",
55
- "react-dom": "^18.0.0 || ^19.0.0"
75
+ "react-dom": "^18.0.0 || ^19.0.0",
76
+ "vue": "^3.3.0"
56
77
  },
57
78
  "peerDependenciesMeta": {
58
79
  "@marsidev/react-turnstile": {
@@ -63,6 +84,9 @@
63
84
  },
64
85
  "react-dom": {
65
86
  "optional": true
87
+ },
88
+ "vue": {
89
+ "optional": true
66
90
  }
67
91
  },
68
92
  "dependencies": {
@@ -77,9 +101,11 @@
77
101
  "@types/node": "^25.0.3",
78
102
  "@types/react": "^19.2.7",
79
103
  "@types/react-dom": "^19.2.3",
104
+ "esbuild-plugin-vue": "^0.2.4",
80
105
  "react": "^19.2.3",
81
106
  "react-dom": "^19.2.3",
82
107
  "tsup": "^8.5.1",
83
- "typescript": "^5.9.3"
108
+ "typescript": "^5.9.3",
109
+ "vue": "^3.5.27"
84
110
  }
85
111
  }