@@ -2,13 +2,23 @@ import { useState } from "react";
22import { Form , ActionPanel , Action , showToast , Toast , popToRoot , Clipboard } from "@raycast/api" ;
33import { useForm , useCachedPromise } from "@raycast/utils" ;
44import { CreateAliasFormData , CreateAliasProps } from "../types" ;
5- import { validateLabel , validateDescription , extractDomainFromEmail } from "../utils" ;
5+ import {
6+ validateLabel ,
7+ validateDescription ,
8+ extractDomainFromEmail ,
9+ generateRandomSlug ,
10+ validateEmail ,
11+ } from "../utils" ;
612import { getApiConfig } from "../services/api/config" ;
713import { getUnusedRules , createRule , updateRule , ensurePoolSize , getAccountDomain } from "../services/cf/rules" ;
814
915export default function CreateAlias ( { alias } : CreateAliasProps = { } ) {
1016 const [ isLoading , setIsLoading ] = useState ( false ) ;
1117 const config = getApiConfig ( ) ;
18+ const [ defaultAliasSlug ] = useState ( ( ) => {
19+ const slug = generateRandomSlug ( ) ;
20+ return config . aliasPreface ? `${ config . aliasPreface } -${ slug } ` : slug ;
21+ } ) ;
1222
1323 // Fetch the correct domain for alias creation
1424 const { data : domain } = useCachedPromise ( async ( ) => {
@@ -29,6 +39,51 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
2939 }
3040 } ) ;
3141
42+ const normalizeAliasInput = ( input : string , aliasDomain : string ) : string => {
43+ const trimmed = input . trim ( ) ;
44+ if ( ! trimmed ) {
45+ throw new Error ( "Alias is required" ) ;
46+ }
47+
48+ if ( trimmed . includes ( "@" ) ) {
49+ const atIndex = trimmed . indexOf ( "@" ) ;
50+ const lastAtIndex = trimmed . lastIndexOf ( "@" ) ;
51+ if ( atIndex !== lastAtIndex ) {
52+ throw new Error ( "Alias must include only one @ symbol" ) ;
53+ }
54+ const localPart = trimmed . slice ( 0 , atIndex ) ;
55+ const inputDomain = trimmed . slice ( atIndex + 1 ) ;
56+ if ( ! localPart || ! inputDomain ) {
57+ throw new Error ( "Alias must include a valid domain" ) ;
58+ }
59+ if ( inputDomain . toLowerCase ( ) !== aliasDomain . toLowerCase ( ) ) {
60+ throw new Error ( `Alias domain must match ${ aliasDomain } ` ) ;
61+ }
62+ return `${ localPart } @${ aliasDomain } ` ;
63+ }
64+
65+ return `${ trimmed } @${ aliasDomain } ` ;
66+ } ;
67+
68+ const validateAlias = ( value ?: string ) : string | undefined => {
69+ if ( alias ) {
70+ return undefined ;
71+ }
72+ if ( ! value ) {
73+ return "Alias is required" ;
74+ }
75+ if ( ! domain ) {
76+ return "Domain not available yet" ;
77+ }
78+
79+ try {
80+ const normalized = normalizeAliasInput ( value , domain as string ) ;
81+ return validateEmail ( normalized ) ? undefined : "Alias must be a valid email address" ;
82+ } catch ( error ) {
83+ return error instanceof Error ? error . message : "Invalid alias" ;
84+ }
85+ } ;
86+
3287 const { handleSubmit, itemProps } = useForm < CreateAliasFormData > ( {
3388 async onSubmit ( values ) {
3489 setIsLoading ( true ) ;
@@ -66,48 +121,52 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
66121 } ) ;
67122 } else {
68123 // Create new alias
124+ const normalizedAlias = normalizeAliasInput ( values . alias , safeDomain ) ;
125+ if ( ! validateEmail ( normalizedAlias ) ) {
126+ throw new Error ( "Alias must be a valid email address" ) ;
127+ }
128+
69129 let unusedRules = await getUnusedRules ( ) ;
130+ let createdEmail = normalizedAlias ;
70131
71- if ( unusedRules . length === 0 ) {
72- // No unused rules available, create a new one
132+ if ( unusedRules . length > 0 ) {
133+ const ruleToUse = unusedRules [ 0 ] ;
134+ const updatedRule = await updateRule ( ruleToUse . id , values . label , values . description , normalizedAlias ) ;
135+ createdEmail = updatedRule . email ;
136+ } else {
73137 showToast ( {
74138 style : Toast . Style . Animated ,
75139 title : "Creating New Alias" ,
76140 message : "Generating new email alias..." ,
77141 } ) ;
78- await createRule ( safeDomain ) ;
142+ const createdRule = await createRule ( safeDomain , normalizedAlias , values . label , values . description ) ;
143+ createdEmail = createdRule . email ;
79144 unusedRules = await getUnusedRules ( ) ;
80145 }
81146
82- if ( unusedRules . length > 0 ) {
83- const ruleToUse = unusedRules [ 0 ] ;
84- await updateRule ( ruleToUse . id , values . label , values . description ) ;
85-
86- showToast ( {
87- style : Toast . Style . Success ,
88- title : "Alias Created" ,
89- message : `Successfully created ${ ruleToUse . email } ` ,
90- primaryAction : {
91- title : "Copy Email" ,
92- onAction : ( ) => {
93- Clipboard . copy ( ruleToUse . email ) ;
94- } ,
147+ await Clipboard . copy ( createdEmail ) ;
148+ showToast ( {
149+ style : Toast . Style . Success ,
150+ title : "Alias Created" ,
151+ message : `Copied ${ createdEmail } to clipboard` ,
152+ primaryAction : {
153+ title : "Copy Email" ,
154+ onAction : ( ) => {
155+ Clipboard . copy ( createdEmail ) ;
95156 } ,
96- } ) ;
157+ } ,
158+ } ) ;
97159
98- // Ensure pool size after using a rule
99- if ( config . preAllocatePool ) {
100- ensurePoolSize ( 20 ) . catch ( ( poolError ) => {
101- console . error ( "Pool size maintenance failed:" , poolError ) ;
102- showToast ( {
103- style : Toast . Style . Failure ,
104- title : "Pool Maintenance Warning" ,
105- message : "Alias created but pool replenishment failed. Next alias creation may be slower." ,
106- } ) ;
160+ // Ensure pool size after using a rule
161+ if ( config . preAllocatePool ) {
162+ ensurePoolSize ( 20 ) . catch ( ( poolError ) => {
163+ console . error ( "Pool size maintenance failed:" , poolError ) ;
164+ showToast ( {
165+ style : Toast . Style . Failure ,
166+ title : "Pool Maintenance Warning" ,
167+ message : "Alias created but pool replenishment failed. Next alias creation may be slower." ,
107168 } ) ;
108- }
109- } else {
110- throw new Error ( "Failed to create or find available alias" ) ;
169+ } ) ;
111170 }
112171 }
113172
@@ -130,10 +189,12 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
130189 }
131190 } ,
132191 initialValues : {
133- label : alias ?. name . label || "" ,
192+ alias : alias ?. email || defaultAliasSlug ,
193+ label : alias ?. name . label || config . defaultLabel || "" ,
134194 description : alias ?. name . description || "" ,
135195 } ,
136196 validation : {
197+ alias : validateAlias ,
137198 label : ( value ) => {
138199 const validation = validateLabel ( value || "" ) ;
139200 return validation . isValid ? undefined : validation . error ;
@@ -174,16 +235,18 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
174235
175236 if ( unusedRules . length > 0 ) {
176237 const ruleToUse = unusedRules [ 0 ] ;
177- await updateRule ( ruleToUse . id , "Quick Alias" , "Created using random unused alias" ) ;
238+ const quickLabel = config . defaultLabel || "Quick Alias" ;
239+ const updatedRule = await updateRule ( ruleToUse . id , quickLabel , "Created using random unused alias" ) ;
178240
241+ await Clipboard . copy ( updatedRule . email ) ;
179242 showToast ( {
180243 style : Toast . Style . Success ,
181244 title : "Alias Created" ,
182- message : `Successfully created ${ ruleToUse . email } ` ,
245+ message : `Copied ${ updatedRule . email } to clipboard ` ,
183246 primaryAction : {
184247 title : "Copy Email" ,
185248 onAction : ( ) => {
186- Clipboard . copy ( ruleToUse . email ) ;
249+ Clipboard . copy ( updatedRule . email ) ;
187250 } ,
188251 } ,
189252 } ) ;
@@ -231,7 +294,15 @@ export default function CreateAlias({ alias }: CreateAliasProps = {}) {
231294 </ ActionPanel >
232295 }
233296 >
234- < Form . TextField title = "Label" placeholder = "Enter a label for this alias (required)" { ...itemProps . label } />
297+ { ! alias && (
298+ < Form . TextField title = "Alias" placeholder = "random-slug or name@example.com" autoFocus { ...itemProps . alias } />
299+ ) }
300+ < Form . TextField
301+ title = "Label"
302+ placeholder = "Enter a label for this alias (required)"
303+ autoFocus = { Boolean ( alias ) }
304+ { ...itemProps . label }
305+ />
235306 < Form . TextArea
236307 title = "Description"
237308 placeholder = "Enter a description for this alias (optional)"
0 commit comments