@pradip1995/commerce-auth 1.0.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/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @pradip1995/commerce-auth
2
+
3
+ Authentication UI and server actions: login, register, OTP, Google OAuth.
4
+
5
+ ```ts
6
+ import { Login, LOGIN_VIEW } from "@pradip1995/commerce-auth"
7
+ ```
8
+
9
+ Consuming apps must provide `@modules/*` shims for icons and modals, or replace theme slots to wrap headless exports.
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@pradip1995/commerce-auth",
3
+ "version": "1.0.0",
4
+ "description": "Medusa storefront authentication — login, register, OTP, Google OAuth",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public",
8
+ "registry": "https://registry.npmjs.org/"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/SmartByteLabs/medusa-storefront-kit.git",
13
+ "directory": "packages/commerce-auth"
14
+ },
15
+ "sideEffects": false,
16
+ "files": [
17
+ "src"
18
+ ],
19
+ "exports": {
20
+ ".": "./src/index.ts",
21
+ "./components/login": "./src/components/login/index.tsx",
22
+ "./components/register": "./src/components/register/index.tsx",
23
+ "./components/forgot-password": "./src/components/forgot-password/index.tsx",
24
+ "./templates/login-template": "./src/templates/login-template.tsx",
25
+ "./types/account": "./src/types/account.ts",
26
+ "./data/customer": "./src/data/customer.ts",
27
+ "./data/customer-registration": "./src/data/customer-registration.ts",
28
+ "./data/guest": "./src/data/guest.ts"
29
+ },
30
+ "peerDependencies": {
31
+ "@medusajs/ui": ">=4",
32
+ "next": ">=15",
33
+ "react": ">=19",
34
+ "react-dom": ">=19",
35
+ "@pradip1995/commerce-core": "1.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@medusajs/types": "latest",
39
+ "@medusajs/ui": "latest",
40
+ "@types/react": "^19",
41
+ "@types/react-dom": "^19",
42
+ "eslint": "^8.57.0",
43
+ "next": "15.3.8",
44
+ "react": "19.0.3",
45
+ "react-dom": "19.0.3",
46
+ "typescript": "^5.7.2",
47
+ "@pradip1995/commerce-core": "1.0.0"
48
+ },
49
+ "scripts": {
50
+ "typecheck": "tsc --noEmit",
51
+ "typecheck:full": "tsc --noEmit -p tsconfig.full.json",
52
+ "lint": "tsc --noEmit"
53
+ }
54
+ }
@@ -0,0 +1,179 @@
1
+ "use client"
2
+
3
+ import { useState } from "react"
4
+ import { LOGIN_VIEW } from "@core/types/account"
5
+ import ErrorMessage from "@modules/checkout/components/error-message"
6
+ import Envelope from "@modules/common/icons/envelope"
7
+ import ArrowLeft from "@modules/common/icons/arrow-left"
8
+ import {
9
+ requestPasswordReset,
10
+ checkEmailRegistered,
11
+ } from "@core/data/customer-registration"
12
+
13
+ type Props = {
14
+ setCurrentView: (view: LOGIN_VIEW) => void
15
+ }
16
+
17
+ const ForgotPassword = ({ setCurrentView }: Props) => {
18
+ const [email, setEmail] = useState("")
19
+ const [isSubmitting, setIsSubmitting] = useState(false)
20
+ const [error, setError] = useState<string | null>(null)
21
+ const [success, setSuccess] = useState(false)
22
+
23
+ const handleSubmit = async (e: React.FormEvent) => {
24
+ e.preventDefault()
25
+ if (!email) return
26
+
27
+ setIsSubmitting(true)
28
+ setError(null)
29
+
30
+ try {
31
+ // First check if the email is registered
32
+ const checkResult = await checkEmailRegistered(email)
33
+ if (!checkResult.exists) {
34
+ setError(
35
+ "This email address is not registered with us. Please sign up to create a new account."
36
+ )
37
+ setIsSubmitting(false)
38
+ return
39
+ }
40
+
41
+ const result = await requestPasswordReset(email)
42
+
43
+ if (result.success) {
44
+ setSuccess(true)
45
+ } else {
46
+ setError(result.error || "Failed to send reset link")
47
+ }
48
+ } catch (err: any) {
49
+ setError(err.message || "An error occurred")
50
+ } finally {
51
+ setIsSubmitting(false)
52
+ }
53
+ }
54
+
55
+ if (success) {
56
+ return (
57
+ <div className="w-full flex flex-col" data-testid="forgot-password-success">
58
+ {/* Success Icon */}
59
+ <div className="flex justify-center mb-4">
60
+ <div className="w-16 h-16 bg-green-100 rounded-full flex items-center justify-center">
61
+ <svg
62
+ className="w-8 h-8 text-green-500"
63
+ fill="none"
64
+ stroke="currentColor"
65
+ viewBox="0 0 24 24"
66
+ >
67
+ <path
68
+ strokeLinecap="round"
69
+ strokeLinejoin="round"
70
+ strokeWidth={2}
71
+ d="M5 13l4 4L19 7"
72
+ />
73
+ </svg>
74
+ </div>
75
+ </div>
76
+
77
+ <h1 className="text-xl min-[340px]:text-2xl min-[550px]:text-3xl font-bold text-heading mb-2 text-center">
78
+ Check Your Email
79
+ </h1>
80
+ <p className="text-sm min-[340px]:text-base text-gray-500 mb-6 text-center">
81
+ We've sent a password reset link to <strong>{email}</strong>. Please check
82
+ your email and click the link to reset your password.
83
+ </p>
84
+
85
+ <button
86
+ onClick={() => setCurrentView(LOGIN_VIEW.SIGN_IN)}
87
+ className="w-full py-2.5 min-[340px]:py-3 px-3 min-[340px]:px-4 bg-brand-accent text-sm min-[340px]:text-base text-inverse font-bold hover:bg-brand-accent-hover transition-colors"
88
+ style={{ borderRadius: "30px" }}
89
+ >
90
+ Back to Login
91
+ </button>
92
+
93
+ <p className="text-xs text-gray-500 mt-4 text-center">
94
+ Didn't receive the email?{" "}
95
+ <button
96
+ onClick={() => setSuccess(false)}
97
+ className="text-brand-accent font-semibold hover:underline"
98
+ >
99
+ Try again
100
+ </button>
101
+ </p>
102
+ </div>
103
+ )
104
+ }
105
+
106
+ return (
107
+ <div className="w-full flex flex-col" data-testid="forgot-password-page">
108
+ {/* Back Button */}
109
+ <button
110
+ type="button"
111
+ onClick={() => setCurrentView(LOGIN_VIEW.SIGN_IN)}
112
+ className="flex items-center gap-2 text-gray-600 hover:text-heading mb-4"
113
+ >
114
+ <ArrowLeft size={20} />
115
+ <span className="text-sm">Back to Login</span>
116
+ </button>
117
+
118
+ {/* Title */}
119
+ <h1 className="text-xl min-[340px]:text-2xl min-[550px]:text-3xl font-bold text-heading mb-1 min-[340px]:mb-2">
120
+ Forgot Password
121
+ </h1>
122
+ <p className="text-sm min-[340px]:text-base text-gray-500 mb-4 min-[340px]:mb-5 min-[550px]:mb-6">
123
+ Enter your email address and we'll send you a link to reset your password.
124
+ </p>
125
+
126
+ <form className="w-full" onSubmit={handleSubmit}>
127
+ {/* Email Input */}
128
+ <div className="mb-4 min-[340px]:mb-5 min-[550px]:mb-6">
129
+ <div className="relative">
130
+ <div className="absolute inset-y-0 left-0 pl-3 min-[340px]:pl-4 flex items-center pointer-events-none">
131
+ <Envelope size="20" color="#9CA3AF" />
132
+ </div>
133
+ <input
134
+ type="email"
135
+ name="email"
136
+ placeholder="Email"
137
+ autoComplete="email"
138
+ required
139
+ value={email}
140
+ onChange={(e) => setEmail(e.target.value)}
141
+ className="w-full pl-10 min-[340px]:pl-12 pr-3 min-[340px]:pr-4 py-2 min-[340px]:py-2.5 min-[550px]:py-3 text-sm min-[340px]:text-base border border-gray-300 focus:outline-none focus:ring-2 focus:ring-brand-accent focus:border-transparent"
142
+ style={{ borderRadius: "30px" }}
143
+ data-testid="forgot-password-email-input"
144
+ />
145
+ </div>
146
+ </div>
147
+
148
+ {/* Error Message */}
149
+ <ErrorMessage error={error} data-testid="forgot-password-error-message" />
150
+
151
+ {/* Submit Button */}
152
+ <button
153
+ type="submit"
154
+ disabled={isSubmitting || !email}
155
+ className="w-full py-2.5 min-[340px]:py-3 px-3 min-[340px]:px-4 bg-brand-accent text-sm min-[340px]:text-base text-inverse font-bold hover:bg-brand-accent-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
156
+ style={{ borderRadius: "30px" }}
157
+ data-testid="send-reset-link-button"
158
+ >
159
+ {isSubmitting ? "Sending..." : "Send Reset Link"}
160
+ </button>
161
+ </form>
162
+
163
+ {/* Login Link */}
164
+ <div className="text-center mt-6">
165
+ <span className="text-gray-700 text-xs min-[340px]:text-sm">
166
+ Remember your password?{" "}
167
+ <button
168
+ onClick={() => setCurrentView(LOGIN_VIEW.SIGN_IN)}
169
+ className="text-brand-accent font-semibold hover:underline"
170
+ >
171
+ Login
172
+ </button>
173
+ </span>
174
+ </div>
175
+ </div>
176
+ )
177
+ }
178
+
179
+ export default ForgotPassword