- Implement Election Materials order-form revisions (docs/lucid *(2).pdf): new "permanent ballot list" question, and a Yes/No/Unsure nonprofit-status question now asked on both blue-envelope permit-ownership branches, with a matching migration, model/repository regen, and validation updates. - Box the /request-order form and heading in the site's form-panel style. - Shrink the site footer. - After request-order and order submission, redirect to / with a flash message instead of re-rendering the form page inline. - Skin the continuation and order-confirmation emails to match the site's branding (logo, navy header/footer, purple accents) instead of plain unstyled HTML. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>master
| @@ -119,6 +119,7 @@ Class OrderApiController_Class | |||||
| ' should not turn a successful submission into a failed one. | ' should not turn a successful submission into a failed one. | ||||
| OrderMailer().SendOrderDetailsEmail order, model | OrderMailer().SendOrderDetailsEmail order, model | ||||
| Flash().Success = "Your order has been submitted. We'll be in touch with your quote soon." | |||||
| Response.Write "{""success"":true}" | Response.Write "{""success"":true}" | ||||
| End Sub | End Sub | ||||
| @@ -93,7 +93,7 @@ Class RequestOrderController_Class | |||||
| OrderMailer().SendContinuationEmail email, token | OrderMailer().SendContinuationEmail email, token | ||||
| Flash().Success = "Check your email for a link to continue your order." | Flash().Success = "Check your email for a link to continue your order." | ||||
| Response.Redirect Routes().AppURL & "request-order" | |||||
| Response.Redirect Routes().AppURL | |||||
| End Sub | End Sub | ||||
| End Class | End Class | ||||
| @@ -19,6 +19,7 @@ Function ValidateOrderDetailsAnswers(answers) | |||||
| RequireEnum errors, answers, "PurplePrintStyle", Array("ColorCoding", "BlackOnly") | RequireEnum errors, answers, "PurplePrintStyle", Array("ColorCoding", "BlackOnly") | ||||
| RequireEnum errors, answers, "PurpleColorCodingBy", Array("Precinct", "Election") | RequireEnum errors, answers, "PurpleColorCodingBy", Array("Precinct", "Election") | ||||
| RequireEnum errors, answers, "BluePermitOwnership", Array("KCIPermit", "OwnPermit") | RequireEnum errors, answers, "BluePermitOwnership", Array("KCIPermit", "OwnPermit") | ||||
| RequireEnum errors, answers, "BlueHasNonprofitStatus", Array("Yes", "No", "Unsure") | |||||
| RequireEnum errors, answers, "MailingPostageOption", Array("FirstClass", "NonprofitRate", "PresortStandard", "No") | RequireEnum errors, answers, "MailingPostageOption", Array("FirstClass", "NonprofitRate", "PresortStandard", "No") | ||||
| RequireEnum errors, answers, "MailingHasNonprofitStatus", Array("Yes", "No", "NotSure") | RequireEnum errors, answers, "MailingHasNonprofitStatus", Array("Yes", "No", "NotSure") | ||||
| @@ -27,9 +28,9 @@ Function ValidateOrderDetailsAnswers(answers) | |||||
| RequireBoolean errors, answers, "PurpleTrackMIBallot" | RequireBoolean errors, answers, "PurpleTrackMIBallot" | ||||
| RequireBoolean errors, answers, "BlueWantsQuote" | RequireBoolean errors, answers, "BlueWantsQuote" | ||||
| RequireBoolean errors, answers, "BluePrintPermit" | RequireBoolean errors, answers, "BluePrintPermit" | ||||
| RequireBoolean errors, answers, "BlueHasNonprofitStatus" | |||||
| RequireBoolean errors, answers, "MailingWantsService" | RequireBoolean errors, answers, "MailingWantsService" | ||||
| RequireNonNegativeInteger errors, answers, "PurplePermanentBallotListCount" | |||||
| RequireNonNegativeInteger errors, answers, "PurplePrecinctCount" | RequireNonNegativeInteger errors, answers, "PurplePrecinctCount" | ||||
| RequireNonNegativeInteger errors, answers, "PurpleExtraEnvelopeQty" | RequireNonNegativeInteger errors, answers, "PurpleExtraEnvelopeQty" | ||||
| RequireNonNegativeInteger errors, answers, "MailingEstimatedQuantity" | RequireNonNegativeInteger errors, answers, "MailingEstimatedQuantity" | ||||
| @@ -65,6 +66,7 @@ Sub MapOrderDetailsAnswers(ByRef model, answers) | |||||
| model.PurpleColorNames = DictValue(answers, "PurpleColorNames") | model.PurpleColorNames = DictValue(answers, "PurpleColorNames") | ||||
| model.PurpleTrackMIBallot = DictValue(answers, "PurpleTrackMIBallot") | model.PurpleTrackMIBallot = DictValue(answers, "PurpleTrackMIBallot") | ||||
| model.PurpleExtraEnvelopeQty = DictValue(answers, "PurpleExtraEnvelopeQty") | model.PurpleExtraEnvelopeQty = DictValue(answers, "PurpleExtraEnvelopeQty") | ||||
| model.PurplePermanentBallotListCount = DictValue(answers, "PurplePermanentBallotListCount") | |||||
| ' Blue envelope | ' Blue envelope | ||||
| model.BlueWantsQuote = DictValue(answers, "BlueWantsQuote") | model.BlueWantsQuote = DictValue(answers, "BlueWantsQuote") | ||||
| @@ -69,6 +69,7 @@ Function LabelNonprofitStatus(code) | |||||
| Case "Yes" : LabelNonprofitStatus = "Yes" | Case "Yes" : LabelNonprofitStatus = "Yes" | ||||
| Case "No" : LabelNonprofitStatus = "No" | Case "No" : LabelNonprofitStatus = "No" | ||||
| Case "NotSure" : LabelNonprofitStatus = "I'm not sure" | Case "NotSure" : LabelNonprofitStatus = "I'm not sure" | ||||
| Case "Unsure" : LabelNonprofitStatus = "Unsure (that's okay, we can help)" | |||||
| Case Else : LabelNonprofitStatus = CStr(code & "") | Case Else : LabelNonprofitStatus = CStr(code & "") | ||||
| End Select | End Select | ||||
| End Function | End Function | ||||
| @@ -1,6 +1,6 @@ | |||||
| <% | <% | ||||
| ' Auto-generated Repository for table [OrderDetails] | ' Auto-generated Repository for table [OrderDetails] | ||||
| ' Generated on 8/4/2026 3:40:46 PM | |||||
| ' Generated on 8/20/2026 11:48:14 AM | |||||
| ' Generator: GenerateRepo.vbs v1.0 | ' Generator: GenerateRepo.vbs v1.0 | ||||
| ' | ' | ||||
| ' Dependencies: | ' Dependencies: | ||||
| @@ -13,7 +13,7 @@ | |||||
| Class OrderDetailsRepository_Class | Class OrderDetailsRepository_Class | ||||
| Public Function FindByID(id) | Public Function FindByID(id) | ||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails] WHERE [OrderDetailID] = ?" | |||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePermanentBallotListCount], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails] WHERE [OrderDetailID] = ?" | |||||
| Dim rs : Set rs = DAL.Query(sql, Array(id)) | Dim rs : Set rs = DAL.Query(sql, Array(id)) | ||||
| If rs.EOF Then | If rs.EOF Then | ||||
| Err.Raise 1, "OrderDetailsRepository_Class", RecordNotFoundException("OrderDetailID", id) | Err.Raise 1, "OrderDetailsRepository_Class", RecordNotFoundException("OrderDetailID", id) | ||||
| @@ -28,7 +28,7 @@ Class OrderDetailsRepository_Class | |||||
| End Function | End Function | ||||
| Public Function Find(where_kvarray, order_string_or_array) | Public Function Find(where_kvarray, order_string_or_array) | ||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePermanentBallotListCount], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim where_keys, where_values, i | Dim where_keys, where_values, i | ||||
| If Not IsEmpty(where_kvarray) Then | If Not IsEmpty(where_kvarray) Then | ||||
| KVUnzip where_kvarray, where_keys, where_values | KVUnzip where_kvarray, where_keys, where_values | ||||
| @@ -52,7 +52,7 @@ Class OrderDetailsRepository_Class | |||||
| End Function | End Function | ||||
| Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | Public Function FindPaged(where_kvarray, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | ||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePermanentBallotListCount], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim where_keys, where_values, i | Dim where_keys, where_values, i | ||||
| If Not IsEmpty(where_kvarray) Then | If Not IsEmpty(where_kvarray) Then | ||||
| KVUnzip where_kvarray, where_keys, where_values | KVUnzip where_kvarray, where_keys, where_values | ||||
| @@ -77,7 +77,7 @@ Class OrderDetailsRepository_Class | |||||
| End Function | End Function | ||||
| Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | Public Function SearchTablePaged(columns_array, search_value, order_string_or_array, per_page, page_num, ByRef page_count, ByRef record_count) | ||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim sql : sql = "Select [BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderDetailID], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePermanentBallotListCount], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt] FROM [OrderDetails]" | |||||
| Dim i, params() | Dim i, params() | ||||
| If IsArray(columns_array) And UBound(columns_array) >= 0 Then | If IsArray(columns_array) And UBound(columns_array) >= 0 Then | ||||
| sql = sql & " WHERE " | sql = sql & " WHERE " | ||||
| @@ -112,9 +112,9 @@ Class OrderDetailsRepository_Class | |||||
| End Function | End Function | ||||
| Public Sub AddNew(ByRef model) | Public Sub AddNew(ByRef model) | ||||
| Dim sql : sql = "INSERT INTO [OrderDetails] ([BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt]) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" | |||||
| DAL.Execute sql, Array(model.BlueDeliveryDate, model.BlueEnvelopeProvider, model.BlueHasNonprofitStatus, model.BlueNonprofitAuthCode, model.BluePermitCity, model.BluePermitClass, model.BluePermitNumber, model.BluePermitOwnership, model.BluePrintPermit, model.BluePrintReturnAddress, model.BlueWantsQuote, model.ContactName, model.FutureVoterStickerRolls, model.IVotedStickerRolls, model.MailingEstimatedQuantity, model.MailingHasNonprofitStatus, model.MailingPickupDate, model.MailingPostageOption, model.MailingWantsService, model.MailingWantsSorting, model.Municipality, model.OrderID, model.Phone, model.PurpleBlueProvider, model.PurpleBlueProviderOther, model.PurpleColorCodingBy, model.PurpleColorNames, model.PurpleDeliveryDate, model.PurpleEnvelopeProvider, model.PurpleEnvelopeStock, model.PurpleExtraEnvelopeQty, model.PurplePrecinctCount, model.PurplePrintOption, model.PurplePrintStyle, model.PurpleTrackMIBallot, model.PurpleWantsBlueEnvelopes, model.PurpleWantsQuote, model.SecrecySleevesQty, model.SpecialRequests, model.SubmittedAt) | |||||
| Dim sql : sql = "INSERT INTO [OrderDetails] ([BlueDeliveryDate], [BlueEnvelopeProvider], [BlueHasNonprofitStatus], [BlueNonprofitAuthCode], [BluePermitCity], [BluePermitClass], [BluePermitNumber], [BluePermitOwnership], [BluePrintPermit], [BluePrintReturnAddress], [BlueWantsQuote], [ContactName], [FutureVoterStickerRolls], [IVotedStickerRolls], [MailingEstimatedQuantity], [MailingHasNonprofitStatus], [MailingPickupDate], [MailingPostageOption], [MailingWantsService], [MailingWantsSorting], [Municipality], [OrderID], [Phone], [PurpleBlueProvider], [PurpleBlueProviderOther], [PurpleColorCodingBy], [PurpleColorNames], [PurpleDeliveryDate], [PurpleEnvelopeProvider], [PurpleEnvelopeStock], [PurpleExtraEnvelopeQty], [PurplePermanentBallotListCount], [PurplePrecinctCount], [PurplePrintOption], [PurplePrintStyle], [PurpleTrackMIBallot], [PurpleWantsBlueEnvelopes], [PurpleWantsQuote], [SecrecySleevesQty], [SpecialRequests], [SubmittedAt]) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" | |||||
| DAL.Execute sql, Array(model.BlueDeliveryDate, model.BlueEnvelopeProvider, model.BlueHasNonprofitStatus, model.BlueNonprofitAuthCode, model.BluePermitCity, model.BluePermitClass, model.BluePermitNumber, model.BluePermitOwnership, model.BluePrintPermit, model.BluePrintReturnAddress, model.BlueWantsQuote, model.ContactName, model.FutureVoterStickerRolls, model.IVotedStickerRolls, model.MailingEstimatedQuantity, model.MailingHasNonprofitStatus, model.MailingPickupDate, model.MailingPostageOption, model.MailingWantsService, model.MailingWantsSorting, model.Municipality, model.OrderID, model.Phone, model.PurpleBlueProvider, model.PurpleBlueProviderOther, model.PurpleColorCodingBy, model.PurpleColorNames, model.PurpleDeliveryDate, model.PurpleEnvelopeProvider, model.PurpleEnvelopeStock, model.PurpleExtraEnvelopeQty, model.PurplePermanentBallotListCount, model.PurplePrecinctCount, model.PurplePrintOption, model.PurplePrintStyle, model.PurpleTrackMIBallot, model.PurpleWantsBlueEnvelopes, model.PurpleWantsQuote, model.SecrecySleevesQty, model.SpecialRequests, model.SubmittedAt) | |||||
| ' Retrieve the newly inserted ID | ' Retrieve the newly inserted ID | ||||
| On Error Resume Next | On Error Resume Next | ||||
| Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty) | Dim rsId : Set rsId = DAL.Query("SELECT @@IDENTITY AS NewID", Empty) | ||||
| @@ -124,7 +124,7 @@ Class OrderDetailsRepository_Class | |||||
| Set rsId = DAL.Query("SELECT TOP 1 [OrderDetailID] FROM [OrderDetails] ORDER BY [OrderDetailID] DESC", Empty) | Set rsId = DAL.Query("SELECT TOP 1 [OrderDetailID] FROM [OrderDetails] ORDER BY [OrderDetailID] DESC", Empty) | ||||
| End If | End If | ||||
| On Error GoTo 0 | On Error GoTo 0 | ||||
| If Not rsId.EOF Then | If Not rsId.EOF Then | ||||
| If Not IsNull(rsId(0)) Then model.OrderDetailID = rsId(0) | If Not IsNull(rsId(0)) Then model.OrderDetailID = rsId(0) | ||||
| End If | End If | ||||
| @@ -132,8 +132,8 @@ Class OrderDetailsRepository_Class | |||||
| End Sub | End Sub | ||||
| Public Sub Update(model) | Public Sub Update(model) | ||||
| Dim sql : sql = "UPDATE [OrderDetails] SET [BlueDeliveryDate] = ?, [BlueEnvelopeProvider] = ?, [BlueHasNonprofitStatus] = ?, [BlueNonprofitAuthCode] = ?, [BluePermitCity] = ?, [BluePermitClass] = ?, [BluePermitNumber] = ?, [BluePermitOwnership] = ?, [BluePrintPermit] = ?, [BluePrintReturnAddress] = ?, [BlueWantsQuote] = ?, [ContactName] = ?, [FutureVoterStickerRolls] = ?, [IVotedStickerRolls] = ?, [MailingEstimatedQuantity] = ?, [MailingHasNonprofitStatus] = ?, [MailingPickupDate] = ?, [MailingPostageOption] = ?, [MailingWantsService] = ?, [MailingWantsSorting] = ?, [Municipality] = ?, [OrderID] = ?, [Phone] = ?, [PurpleBlueProvider] = ?, [PurpleBlueProviderOther] = ?, [PurpleColorCodingBy] = ?, [PurpleColorNames] = ?, [PurpleDeliveryDate] = ?, [PurpleEnvelopeProvider] = ?, [PurpleEnvelopeStock] = ?, [PurpleExtraEnvelopeQty] = ?, [PurplePrecinctCount] = ?, [PurplePrintOption] = ?, [PurplePrintStyle] = ?, [PurpleTrackMIBallot] = ?, [PurpleWantsBlueEnvelopes] = ?, [PurpleWantsQuote] = ?, [SecrecySleevesQty] = ?, [SpecialRequests] = ?, [SubmittedAt] = ? WHERE [OrderDetailID] = ?" | |||||
| DAL.Execute sql, Array(model.BlueDeliveryDate, model.BlueEnvelopeProvider, model.BlueHasNonprofitStatus, model.BlueNonprofitAuthCode, model.BluePermitCity, model.BluePermitClass, model.BluePermitNumber, model.BluePermitOwnership, model.BluePrintPermit, model.BluePrintReturnAddress, model.BlueWantsQuote, model.ContactName, model.FutureVoterStickerRolls, model.IVotedStickerRolls, model.MailingEstimatedQuantity, model.MailingHasNonprofitStatus, model.MailingPickupDate, model.MailingPostageOption, model.MailingWantsService, model.MailingWantsSorting, model.Municipality, model.OrderID, model.Phone, model.PurpleBlueProvider, model.PurpleBlueProviderOther, model.PurpleColorCodingBy, model.PurpleColorNames, model.PurpleDeliveryDate, model.PurpleEnvelopeProvider, model.PurpleEnvelopeStock, model.PurpleExtraEnvelopeQty, model.PurplePrecinctCount, model.PurplePrintOption, model.PurplePrintStyle, model.PurpleTrackMIBallot, model.PurpleWantsBlueEnvelopes, model.PurpleWantsQuote, model.SecrecySleevesQty, model.SpecialRequests, model.SubmittedAt, model.OrderDetailID) | |||||
| Dim sql : sql = "UPDATE [OrderDetails] SET [BlueDeliveryDate] = ?, [BlueEnvelopeProvider] = ?, [BlueHasNonprofitStatus] = ?, [BlueNonprofitAuthCode] = ?, [BluePermitCity] = ?, [BluePermitClass] = ?, [BluePermitNumber] = ?, [BluePermitOwnership] = ?, [BluePrintPermit] = ?, [BluePrintReturnAddress] = ?, [BlueWantsQuote] = ?, [ContactName] = ?, [FutureVoterStickerRolls] = ?, [IVotedStickerRolls] = ?, [MailingEstimatedQuantity] = ?, [MailingHasNonprofitStatus] = ?, [MailingPickupDate] = ?, [MailingPostageOption] = ?, [MailingWantsService] = ?, [MailingWantsSorting] = ?, [Municipality] = ?, [OrderID] = ?, [Phone] = ?, [PurpleBlueProvider] = ?, [PurpleBlueProviderOther] = ?, [PurpleColorCodingBy] = ?, [PurpleColorNames] = ?, [PurpleDeliveryDate] = ?, [PurpleEnvelopeProvider] = ?, [PurpleEnvelopeStock] = ?, [PurpleExtraEnvelopeQty] = ?, [PurplePermanentBallotListCount] = ?, [PurplePrecinctCount] = ?, [PurplePrintOption] = ?, [PurplePrintStyle] = ?, [PurpleTrackMIBallot] = ?, [PurpleWantsBlueEnvelopes] = ?, [PurpleWantsQuote] = ?, [SecrecySleevesQty] = ?, [SpecialRequests] = ?, [SubmittedAt] = ? WHERE [OrderDetailID] = ?" | |||||
| DAL.Execute sql, Array(model.BlueDeliveryDate, model.BlueEnvelopeProvider, model.BlueHasNonprofitStatus, model.BlueNonprofitAuthCode, model.BluePermitCity, model.BluePermitClass, model.BluePermitNumber, model.BluePermitOwnership, model.BluePrintPermit, model.BluePrintReturnAddress, model.BlueWantsQuote, model.ContactName, model.FutureVoterStickerRolls, model.IVotedStickerRolls, model.MailingEstimatedQuantity, model.MailingHasNonprofitStatus, model.MailingPickupDate, model.MailingPostageOption, model.MailingWantsService, model.MailingWantsSorting, model.Municipality, model.OrderID, model.Phone, model.PurpleBlueProvider, model.PurpleBlueProviderOther, model.PurpleColorCodingBy, model.PurpleColorNames, model.PurpleDeliveryDate, model.PurpleEnvelopeProvider, model.PurpleEnvelopeStock, model.PurpleExtraEnvelopeQty, model.PurplePermanentBallotListCount, model.PurplePrecinctCount, model.PurplePrintOption, model.PurplePrintStyle, model.PurpleTrackMIBallot, model.PurpleWantsBlueEnvelopes, model.PurpleWantsQuote, model.SecrecySleevesQty, model.SpecialRequests, model.SubmittedAt, model.OrderDetailID) | |||||
| End Sub | End Sub | ||||
| Public Sub Delete(id) | Public Sub Delete(id) | ||||
| @@ -9,13 +9,18 @@ Class OrderMailer_Class | |||||
| Dim continueUrl | Dim continueUrl | ||||
| continueUrl = Routes().AppURL & "order/continue?token=" & Server.URLEncode(token) | continueUrl = Routes().AppURL & "order/continue?token=" & Server.URLEncode(token) | ||||
| Dim inner | |||||
| inner = "<div style=""color:#7c3bc0; font-weight:800; font-size:11px; letter-spacing:0.06em; text-transform:uppercase;"">Order Request Received</div>" & _ | |||||
| "<h1 style=""color:#201a27; font-size:22px; margin:6px 0 12px; font-family:Arial, Helvetica, sans-serif;"">Continue your election materials order</h1>" & _ | |||||
| "<p style=""color:#6d6574; font-size:13px; margin:0 0 20px;"">Click the button below to pick up where you left off.</p>" & _ | |||||
| "<p style=""margin:0 0 20px;""><a href=""" & H(continueUrl) & """ style=""display:inline-block; background:#6529a0; color:#ffffff; font-weight:700; font-size:14px; padding:12px 28px; border-radius:10px; text-decoration:none;"">Continue your order</a></p>" & _ | |||||
| "<p style=""color:#6d6574; font-size:12px; margin:0;"">Or copy and paste this link into your browser:<br /><a href=""" & H(continueUrl) & """ style=""color:#6529a0;"">" & H(continueUrl) & "</a></p>" & _ | |||||
| "<p style=""color:#6d6574; font-size:12px; margin-top:16px;"">This link expires in " & H(GetAppSetting("OrderTokenExpirationHours")) & " hours and can only be used once.</p>" | |||||
| Dim mail : Set mail = NewConfiguredMail() | Dim mail : Set mail = NewConfiguredMail() | ||||
| mail.Subject = "Continue your Purple Envelope order" | mail.Subject = "Continue your Purple Envelope order" | ||||
| mail.IsBodyHTML = True | mail.IsBodyHTML = True | ||||
| mail.Body = "<p>Click the link below to continue your order:</p>" & _ | |||||
| "<p><a href=""" & H(continueUrl) & """>" & H(continueUrl) & "</a></p>" & _ | |||||
| "<p>This link expires in " & H(GetAppSetting("OrderTokenExpirationHours")) & _ | |||||
| " hours and can only be used once.</p>" | |||||
| mail.Body = EmailShell(inner) | |||||
| mail.AddRecipient "To", email | mail.AddRecipient "To", email | ||||
| mail.Send | mail.Send | ||||
| End Sub | End Sub | ||||
| @@ -55,14 +60,7 @@ Class OrderMailer_Class | |||||
| Private Function BuildOrderDetailsEmailBody(order, model) | Private Function BuildOrderDetailsEmailBody(order, model) | ||||
| Dim html | Dim html | ||||
| html = "<!doctype html><html><body style=""margin:0; padding:0; background:#faf7fd; font-family:Arial, Helvetica, sans-serif;"">" & _ | |||||
| "<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" style=""background:#faf7fd; padding:24px 0;""><tr><td align=""center"">" & _ | |||||
| "<table role=""presentation"" width=""600"" cellpadding=""0"" cellspacing=""0"" style=""background:#ffffff; border-radius:12px; overflow:hidden; box-shadow:0 4px 16px rgba(36,16,58,0.08);"">" & _ | |||||
| "<tr><td style=""background:#24103a; padding:24px 32px;"">" & _ | |||||
| "<span style=""color:#ffffff; font-weight:700; font-size:18px;"">KCI Purple Envelope Order Confirmation</span>" & _ | |||||
| "</td></tr>" & _ | |||||
| "<tr><td style=""padding:32px;"">" & _ | |||||
| "<div style=""color:#7c3bc0; font-weight:800; font-size:11px; letter-spacing:0.06em; text-transform:uppercase;"">Order Received</div>" & _ | |||||
| html = "<div style=""color:#7c3bc0; font-weight:800; font-size:11px; letter-spacing:0.06em; text-transform:uppercase;"">Order Received</div>" & _ | |||||
| "<h1 style=""color:#201a27; font-size:22px; margin:6px 0 4px; font-family:Arial, Helvetica, sans-serif;"">Thanks! We've received your order details.</h1>" & _ | "<h1 style=""color:#201a27; font-size:22px; margin:6px 0 4px; font-family:Arial, Helvetica, sans-serif;"">Thanks! We've received your order details.</h1>" & _ | ||||
| "<p style=""color:#6d6574; font-size:13px; margin:0 0 8px;"">Jurisdiction number <strong style=""color:#201a27;"">" & H(order("JurisdictionNumber")) & "</strong>" & _ | "<p style=""color:#6d6574; font-size:13px; margin:0 0 8px;"">Jurisdiction number <strong style=""color:#201a27;"">" & H(order("JurisdictionNumber")) & "</strong>" & _ | ||||
| " · submitted " & H(EmailDate(model.SubmittedAt)) & "</p>" | " · submitted " & H(EmailDate(model.SubmittedAt)) & "</p>" | ||||
| @@ -80,6 +78,7 @@ Class OrderMailer_Class | |||||
| End If | End If | ||||
| html = html & EmailSection("Purple Ballot Envelopes", _ | html = html & EmailSection("Purple Ballot Envelopes", _ | ||||
| EmailRow("Permanent ballot list count", model.PurplePermanentBallotListCount) & _ | |||||
| EmailRow("Envelope stock", LabelEnvelopeStock(model.PurpleEnvelopeStock)) & _ | EmailRow("Envelope stock", LabelEnvelopeStock(model.PurpleEnvelopeStock)) & _ | ||||
| EmailRow("Print option", LabelPrintOption(model.PurplePrintOption)) & _ | EmailRow("Print option", LabelPrintOption(model.PurplePrintOption)) & _ | ||||
| EmailRow("Also wants KCI blue envelopes", EmailYesNo(model.PurpleWantsBlueEnvelopes)) & _ | EmailRow("Also wants KCI blue envelopes", EmailYesNo(model.PurpleWantsBlueEnvelopes)) & _ | ||||
| @@ -93,24 +92,25 @@ Class OrderMailer_Class | |||||
| End If | End If | ||||
| If IsTrue(model.BlueWantsQuote) Then | If IsTrue(model.BlueWantsQuote) Then | ||||
| ' City/permit number are always collected for an own-organization permit - a | |||||
| ' permit lookup can't be done from the nonprofit authorization code alone, so | |||||
| ' that code is additional information when nonprofit status is Yes, not a | |||||
| ' replacement for city/permit number. | |||||
| ' Nonprofit status (and the auth code that follows a "Yes") is asked for either | |||||
| ' permit-ownership branch. City/permit number are additionally collected for an | |||||
| ' own-organization permit - a permit lookup can't be done from the nonprofit | |||||
| ' authorization code alone. | |||||
| Dim bluePermitLine | Dim bluePermitLine | ||||
| bluePermitLine = "" | bluePermitLine = "" | ||||
| If CStr(model.BlueHasNonprofitStatus & "") = "Yes" Then | |||||
| bluePermitLine = EmailRow("Nonprofit authorization code", model.BlueNonprofitAuthCode) | |||||
| End If | |||||
| If LabelPermitOwnership(model.BluePermitOwnership) = "My organization's permit" Then | If LabelPermitOwnership(model.BluePermitOwnership) = "My organization's permit" Then | ||||
| bluePermitLine = EmailRow("City of permit holder", model.BluePermitCity) & _ | |||||
| bluePermitLine = bluePermitLine & _ | |||||
| EmailRow("City of permit holder", model.BluePermitCity) & _ | |||||
| EmailRow("Permit number", model.BluePermitNumber) | EmailRow("Permit number", model.BluePermitNumber) | ||||
| If IsTrue(model.BlueHasNonprofitStatus) Then | |||||
| bluePermitLine = bluePermitLine & EmailRow("Nonprofit authorization code", model.BlueNonprofitAuthCode) | |||||
| End If | |||||
| End If | End If | ||||
| html = html & EmailSection("Blue AV Envelopes", _ | html = html & EmailSection("Blue AV Envelopes", _ | ||||
| EmailRow("Print permit", EmailYesNo(model.BluePrintPermit)) & _ | EmailRow("Print permit", EmailYesNo(model.BluePrintPermit)) & _ | ||||
| EmailRow("Permit ownership", LabelPermitOwnership(model.BluePermitOwnership)) & _ | EmailRow("Permit ownership", LabelPermitOwnership(model.BluePermitOwnership)) & _ | ||||
| EmailRow("Confirmed nonprofit status with USPS", EmailYesNo(model.BlueHasNonprofitStatus)) & _ | |||||
| EmailRow("Confirmed nonprofit status with USPS", LabelNonprofitStatus(model.BlueHasNonprofitStatus)) & _ | |||||
| bluePermitLine) | bluePermitLine) | ||||
| End If | End If | ||||
| @@ -130,14 +130,27 @@ Class OrderMailer_Class | |||||
| EmailRow("Special requests", model.SpecialRequests)) | EmailRow("Special requests", model.SpecialRequests)) | ||||
| End If | End If | ||||
| html = html & "<p style=""color:#6d6574; font-size:12px; margin-top:24px;"">Questions about this order? Just reply to this email.</p>" & _ | |||||
| html = html & "<p style=""color:#6d6574; font-size:12px; margin-top:24px;"">Questions about this order? Just reply to this email.</p>" | |||||
| BuildOrderDetailsEmailBody = EmailShell(html) | |||||
| End Function | |||||
| ' Wraps inner body HTML in the same header/footer chrome used across the website - the | |||||
| ' navy KCI Election Printing bar (matching the site header/footer) and white content card. | |||||
| Private Function EmailShell(innerHtml) | |||||
| Dim logoUrl : logoUrl = Routes().AppURL & "images/KCI%20ELECTION%20printing%20(white).png" | |||||
| EmailShell = "<!doctype html><html><body style=""margin:0; padding:0; background:#faf7fd; font-family:Arial, Helvetica, sans-serif;"">" & _ | |||||
| "<table role=""presentation"" width=""100%"" cellpadding=""0"" cellspacing=""0"" style=""background:#faf7fd; padding:24px 0;""><tr><td align=""center"">" & _ | |||||
| "<table role=""presentation"" width=""600"" cellpadding=""0"" cellspacing=""0"" style=""background:#ffffff; border-radius:12px; overflow:hidden; box-shadow:0 4px 16px rgba(36,16,58,0.08);"">" & _ | |||||
| "<tr><td style=""background:#254B74; padding:20px 32px; text-align:center;"">" & _ | |||||
| "<img src=""" & H(logoUrl) & """ alt=""KCI Election Printing"" width=""160"" style=""display:block; margin:0 auto; height:auto; max-width:160px;"" />" & _ | |||||
| "</td></tr>" & _ | "</td></tr>" & _ | ||||
| "<tr><td style=""background:#f0e7f8; padding:16px 32px; text-align:center;"">" & _ | |||||
| "<span style=""color:#6529a0; font-size:11px;"">Purple Envelope · Addressing & Barcoding</span>" & _ | |||||
| "<tr><td style=""padding:32px;"">" & innerHtml & "</td></tr>" & _ | |||||
| "<tr><td style=""background:#254B74; padding:20px 32px; text-align:center;"">" & _ | |||||
| "<div style=""color:#ffffff; font-size:12px; font-weight:700;"">KCI Election Printing</div>" & _ | |||||
| "<div style=""color:rgba(255,255,255,0.75); font-size:11px; margin-top:4px;"">616.957.2120 · info@kentcommunications.com</div>" & _ | |||||
| "</td></tr>" & _ | "</td></tr>" & _ | ||||
| "</table></td></tr></table></body></html>" | "</table></td></tr></table></body></html>" | ||||
| BuildOrderDetailsEmailBody = html | |||||
| End Function | End Function | ||||
| Private Function EmailSection(sectionTitle, rowsHtml) | Private Function EmailSection(sectionTitle, rowsHtml) | ||||
| @@ -42,6 +42,7 @@ Class POBO_OrderDetails | |||||
| Private pPurpleEnvelopeProvider | Private pPurpleEnvelopeProvider | ||||
| Private pPurpleEnvelopeStock | Private pPurpleEnvelopeStock | ||||
| Private pPurpleExtraEnvelopeQty | Private pPurpleExtraEnvelopeQty | ||||
| Private pPurplePermanentBallotListCount | |||||
| Private pPurplePrecinctCount | Private pPurplePrecinctCount | ||||
| Private pPurplePrintOption | Private pPurplePrintOption | ||||
| Private pPurplePrintStyle | Private pPurplePrintStyle | ||||
| @@ -85,6 +86,7 @@ Class POBO_OrderDetails | |||||
| pPurpleEnvelopeProvider = Null | pPurpleEnvelopeProvider = Null | ||||
| pPurpleEnvelopeStock = Null | pPurpleEnvelopeStock = Null | ||||
| pPurpleExtraEnvelopeQty = 0 | pPurpleExtraEnvelopeQty = 0 | ||||
| pPurplePermanentBallotListCount = 0 | |||||
| pPurplePrecinctCount = 0 | pPurplePrecinctCount = 0 | ||||
| pPurplePrintOption = Null | pPurplePrintOption = Null | ||||
| pPurplePrintStyle = Null | pPurplePrintStyle = Null | ||||
| @@ -94,7 +96,7 @@ Class POBO_OrderDetails | |||||
| pSecrecySleevesQty = 0 | pSecrecySleevesQty = 0 | ||||
| pSpecialRequests = Null | pSpecialRequests = Null | ||||
| pSubmittedAt = #1/1/1970# | pSubmittedAt = #1/1/1970# | ||||
| Properties = Array("BlueDeliveryDate","BlueEnvelopeProvider","BlueHasNonprofitStatus","BlueNonprofitAuthCode","BluePermitCity","BluePermitClass","BluePermitNumber","BluePermitOwnership","BluePrintPermit","BluePrintReturnAddress","BlueWantsQuote","ContactName","FutureVoterStickerRolls","IVotedStickerRolls","MailingEstimatedQuantity","MailingHasNonprofitStatus","MailingPickupDate","MailingPostageOption","MailingWantsService","MailingWantsSorting","Municipality","OrderDetailID","OrderID","Phone","PurpleBlueProvider","PurpleBlueProviderOther","PurpleColorCodingBy","PurpleColorNames","PurpleDeliveryDate","PurpleEnvelopeProvider","PurpleEnvelopeStock","PurpleExtraEnvelopeQty","PurplePrecinctCount","PurplePrintOption","PurplePrintStyle","PurpleTrackMIBallot","PurpleWantsBlueEnvelopes","PurpleWantsQuote","SecrecySleevesQty","SpecialRequests","SubmittedAt") | |||||
| Properties = Array("BlueDeliveryDate","BlueEnvelopeProvider","BlueHasNonprofitStatus","BlueNonprofitAuthCode","BluePermitCity","BluePermitClass","BluePermitNumber","BluePermitOwnership","BluePrintPermit","BluePrintReturnAddress","BlueWantsQuote","ContactName","FutureVoterStickerRolls","IVotedStickerRolls","MailingEstimatedQuantity","MailingHasNonprofitStatus","MailingPickupDate","MailingPostageOption","MailingWantsService","MailingWantsSorting","Municipality","OrderDetailID","OrderID","Phone","PurpleBlueProvider","PurpleBlueProviderOther","PurpleColorCodingBy","PurpleColorNames","PurpleDeliveryDate","PurpleEnvelopeProvider","PurpleEnvelopeStock","PurpleExtraEnvelopeQty","PurplePermanentBallotListCount","PurplePrecinctCount","PurplePrintOption","PurplePrintStyle","PurpleTrackMIBallot","PurpleWantsBlueEnvelopes","PurpleWantsQuote","SecrecySleevesQty","SpecialRequests","SubmittedAt") | |||||
| End Sub | End Sub | ||||
| Public Property Get PrimaryKey() | Public Property Get PrimaryKey() | ||||
| @@ -145,10 +147,10 @@ Class POBO_OrderDetails | |||||
| Public Property Let BlueHasNonprofitStatus(val) | Public Property Let BlueHasNonprofitStatus(val) | ||||
| On Error Resume Next | On Error Resume Next | ||||
| If IsNull(val) Then | |||||
| pBlueHasNonprofitStatus = Null | |||||
| If IsNumeric(val) Then | |||||
| pBlueHasNonprofitStatus = CDbl(val) | |||||
| Else | Else | ||||
| pBlueHasNonprofitStatus = CBool(val) | |||||
| pBlueHasNonprofitStatus = val | |||||
| End If | End If | ||||
| If Err.Number <> 0 Then | If Err.Number <> 0 Then | ||||
| Err.Raise Err.Number, "POBO_OrderDetails.BlueHasNonprofitStatus", "Invalid value for BlueHasNonprofitStatus: " & Err.Description | Err.Raise Err.Number, "POBO_OrderDetails.BlueHasNonprofitStatus", "Invalid value for BlueHasNonprofitStatus: " & Err.Description | ||||
| @@ -637,6 +639,23 @@ Class POBO_OrderDetails | |||||
| On Error GoTo 0 | On Error GoTo 0 | ||||
| End Property | End Property | ||||
| Public Property Get PurplePermanentBallotListCount() | |||||
| PurplePermanentBallotListCount = pPurplePermanentBallotListCount | |||||
| End Property | |||||
| Public Property Let PurplePermanentBallotListCount(val) | |||||
| On Error Resume Next | |||||
| If IsNumeric(val) Then | |||||
| pPurplePermanentBallotListCount = CDbl(val) | |||||
| Else | |||||
| pPurplePermanentBallotListCount = val | |||||
| End If | |||||
| If Err.Number <> 0 Then | |||||
| Err.Raise Err.Number, "POBO_OrderDetails.PurplePermanentBallotListCount", "Invalid value for PurplePermanentBallotListCount: " & Err.Description | |||||
| End If | |||||
| On Error GoTo 0 | |||||
| End Property | |||||
| Public Property Get PurplePrecinctCount() | Public Property Get PurplePrecinctCount() | ||||
| PurplePrecinctCount = pPurplePrecinctCount | PurplePrecinctCount = pPurplePrecinctCount | ||||
| End Property | End Property | ||||
| @@ -96,6 +96,13 @@ | |||||
| title: "Would you like for KCI to print your purple AV envelopes?", | title: "Would you like for KCI to print your purple AV envelopes?", | ||||
| isRequired: true | isRequired: true | ||||
| }, | }, | ||||
| { | |||||
| type: "text", name: "PurplePermanentBallotListCount", | |||||
| title: "How many are on your permanent ballot list?", | |||||
| inputType: "number", min: 0, | |||||
| visibleIf: "{PurpleWantsQuote} = true", | |||||
| isRequired: true | |||||
| }, | |||||
| { | { | ||||
| type: "radiogroup", name: "PurpleEnvelopeStock", | type: "radiogroup", name: "PurpleEnvelopeStock", | ||||
| title: "Are you planning to use KCI's purple envelopes or from your own stock?", | title: "Are you planning to use KCI's purple envelopes or from your own stock?", | ||||
| @@ -229,15 +236,20 @@ | |||||
| html: "<p><strong>Please note, envelopes with KCI's permit MUST be mailed from KCI.</strong></p>" | html: "<p><strong>Please note, envelopes with KCI's permit MUST be mailed from KCI.</strong></p>" | ||||
| }, | }, | ||||
| { | { | ||||
| type: "boolean", name: "BlueHasNonprofitStatus", labelTrue: "Yes", labelFalse: "No", | |||||
| type: "radiogroup", name: "BlueHasNonprofitStatus", | |||||
| title: "Does your organization have confirmed nonprofit status with USPS?", | title: "Does your organization have confirmed nonprofit status with USPS?", | ||||
| visibleIf: "{BluePermitOwnership} = 'OwnPermit'", | |||||
| isRequired: true | |||||
| visibleIf: "{BluePrintPermit} = true", | |||||
| isRequired: true, | |||||
| choices: [ | |||||
| { value: "Yes", text: "Yes" }, | |||||
| { value: "No", text: "No" }, | |||||
| { value: "Unsure", text: "Unsure (That's okay, we can help you!)" } | |||||
| ] | |||||
| }, | }, | ||||
| { | { | ||||
| type: "text", name: "BlueNonprofitAuthCode", | type: "text", name: "BlueNonprofitAuthCode", | ||||
| title: "Please provide your nonprofit authorization code.", | title: "Please provide your nonprofit authorization code.", | ||||
| visibleIf: "{BlueHasNonprofitStatus} = true", | |||||
| visibleIf: "{BlueHasNonprofitStatus} = 'Yes'", | |||||
| isRequired: true | isRequired: true | ||||
| }, | }, | ||||
| { | { | ||||
| @@ -442,12 +454,8 @@ | |||||
| }); | }); | ||||
| }) | }) | ||||
| .then(function (result) { | .then(function (result) { | ||||
| var container = document.getElementById("surveyContainer"); | |||||
| if (result.ok && result.body && result.body.success) { | if (result.ok && result.body && result.body.success) { | ||||
| container.innerHTML = | |||||
| '<p style="text-align:center; padding: 32px 0;">' + | |||||
| "Thank you for submitting your order." + | |||||
| "</p>"; | |||||
| window.location.href = "<%= H(Routes().AppURL) %>"; | |||||
| } else { | } else { | ||||
| alert((result.body && result.body.error) || "Something went wrong submitting your order. Please try again."); | alert((result.body && result.body.error) || "Something went wrong submitting your order. Please try again."); | ||||
| } | } | ||||
| @@ -1,32 +1,34 @@ | |||||
| <section style="padding: 48px 0; max-width: 640px; margin: 0 auto;"> | <section style="padding: 48px 0; max-width: 640px; margin: 0 auto;"> | ||||
| <div class="section-heading"> | |||||
| <span class="eyebrow">Start your order</span> | |||||
| <h1>Request an order</h1> | |||||
| <p> | |||||
| Enter your email address and jurisdiction number below. We'll email you a | |||||
| secure link to continue your order. | |||||
| </p> | |||||
| </div> | |||||
| <div class="form-panel"> | |||||
| <div class="section-heading form-section"> | |||||
| <span class="eyebrow">Start your order</span> | |||||
| <h1>Request an order</h1> | |||||
| <p> | |||||
| Enter your email address and jurisdiction number below. We'll email you a | |||||
| secure link to continue your order. | |||||
| </p> | |||||
| </div> | |||||
| <form method="post" action="<%= H(Routes().AppURL & "request-order") %>"> | |||||
| <input type="hidden" name="csrf_token" value="<%= H(csrfToken) %>" /> | |||||
| <form method="post" class="form-section" action="<%= H(Routes().AppURL & "request-order") %>"> | |||||
| <input type="hidden" name="csrf_token" value="<%= H(csrfToken) %>" /> | |||||
| <div class="form-group" style="margin-bottom: 16px;"> | |||||
| <label for="Email">Email address</label> | |||||
| <input type="email" class="form-control" id="Email" name="Email" required | |||||
| value="<%= H(emailValue) %>" /> | |||||
| </div> | |||||
| <div class="form-group" style="margin-bottom: 16px;"> | |||||
| <label for="Email">Email address</label> | |||||
| <input type="email" class="form-control" id="Email" name="Email" required | |||||
| value="<%= H(emailValue) %>" /> | |||||
| </div> | |||||
| <div class="form-group" style="margin-bottom: 24px;"> | |||||
| <label for="JurisdictionNumber">Jurisdiction number</label> | |||||
| <input type="text" class="form-control" id="JurisdictionNumber" name="JurisdictionNumber" required | |||||
| value="<%= H(jurisdictionValue) %>" /> | |||||
| </div> | |||||
| <div class="form-group" style="margin-bottom: 24px;"> | |||||
| <label for="JurisdictionNumber">Jurisdiction number</label> | |||||
| <input type="text" class="form-control" id="JurisdictionNumber" name="JurisdictionNumber" required | |||||
| value="<%= H(jurisdictionValue) %>" /> | |||||
| </div> | |||||
| <div class="cf-turnstile" style="margin-bottom: 24px;" data-sitekey="<%= H(turnstileSiteKey) %>"></div> | |||||
| <div class="cf-turnstile" style="margin-bottom: 24px;" data-sitekey="<%= H(turnstileSiteKey) %>"></div> | |||||
| <button type="submit" class="button button-primary">Request order</button> | |||||
| </form> | |||||
| <button type="submit" class="button button-primary">Request order</button> | |||||
| </form> | |||||
| </div> | |||||
| </section> | </section> | ||||
| <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> | <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> | ||||
| @@ -0,0 +1,44 @@ | |||||
| <% | |||||
| '======================================================================================================================= | |||||
| ' MIGRATION: add_purple_ballot_list_count_and_blue_nonprofit_options | |||||
| '======================================================================================================================= | |||||
| ' This migration was auto-generated. Add your migration logic below. | |||||
| ' | |||||
| ' The migration object provides these helper methods: | |||||
| ' - migration.ExecuteSQL(sql) - Execute raw SQL | |||||
| ' - migration.CreateTable(name, columns) - Create a table | |||||
| ' - migration.DropTable(name) - Drop a table | |||||
| ' - migration.AddColumn(table, column, type) - Add a column | |||||
| ' - migration.DropColumn(table, column) - Drop a column | |||||
| ' - migration.CreateIndex(name, table, columns) - Create an index | |||||
| ' - migration.DropIndex(name, table) - Drop an index | |||||
| ' | |||||
| ' For complex operations, use migration.DB to access the database directly: | |||||
| ' migration.DB.Execute "INSERT INTO users (name) VALUES (?)", Array("John") | |||||
| ' | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' UP - Apply the migration | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Sub Migration_Up(migration) | |||||
| ' Fields added/changed by the revised order-form PDFs (docs/lucid/*"(2).pdf"). | |||||
| ' Purple Ballot Envelope section - new "permanent ballot list" count, asked | |||||
| ' alongside PurpleEnvelopeStock/PurplePrintOption. | |||||
| migration.AddColumn "OrderDetails", "PurplePermanentBallotListCount", "INTEGER" | |||||
| ' Blue AV Envelope section - BlueHasNonprofitStatus gained a third "Unsure" choice and | |||||
| ' is now asked for both permit-ownership branches, so it can no longer be YESNO. | |||||
| ' Existing YESNO test data converts to "-1"/"0" text under this type change - there's no | |||||
| ' real order data yet, so that's acceptable here. | |||||
| migration.ExecuteSQL "ALTER TABLE OrderDetails ALTER COLUMN BlueHasNonprofitStatus VARCHAR(20)" | |||||
| End Sub | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| ' DOWN - Rollback the migration | |||||
| '----------------------------------------------------------------------------------------------------------------------- | |||||
| Sub Migration_Down(migration) | |||||
| migration.ExecuteSQL "ALTER TABLE OrderDetails ALTER COLUMN BlueHasNonprofitStatus YESNO" | |||||
| migration.DropColumn "OrderDetails", "PurplePermanentBallotListCount" | |||||
| End Sub | |||||
| %> | |||||
| @@ -847,7 +847,7 @@ details p { | |||||
| } | } | ||||
| .site-footer { | .site-footer { | ||||
| flex: 0 0 auto; | flex: 0 0 auto; | ||||
| padding: 6px 0 4px; | |||||
| padding: 3px 0 2px; | |||||
| color: rgba(255,255,255,0.84); | color: rgba(255,255,255,0.84); | ||||
| background: #254B74; | background: #254B74; | ||||
| border-top: 1px solid rgba(255, 255, 255, 0.12); | border-top: 1px solid rgba(255, 255, 255, 0.12); | ||||
| @@ -855,19 +855,19 @@ details p { | |||||
| .footer-shell { | .footer-shell { | ||||
| display: flex; | display: flex; | ||||
| flex-direction: column; | flex-direction: column; | ||||
| gap: 4px; | |||||
| gap: 2px; | |||||
| } | } | ||||
| .footer-compact-row { | .footer-compact-row { | ||||
| display: grid; | display: grid; | ||||
| grid-template-columns: auto 1fr auto; | grid-template-columns: auto 1fr auto; | ||||
| align-items: center; | align-items: center; | ||||
| gap: 10px; | |||||
| gap: 6px; | |||||
| } | } | ||||
| .footer-logo-inline img { | .footer-logo-inline img { | ||||
| display: block; | display: block; | ||||
| width: 104px; | |||||
| width: 56px; | |||||
| height: auto; | height: auto; | ||||
| } | } | ||||
| @@ -875,9 +875,9 @@ details p { | |||||
| display: flex; | display: flex; | ||||
| align-items: center; | align-items: center; | ||||
| flex-wrap: wrap; | flex-wrap: wrap; | ||||
| gap: 6px 12px; | |||||
| font-size: 0.62rem; | |||||
| line-height: 1.2; | |||||
| gap: 3px 8px; | |||||
| font-size: 0.52rem; | |||||
| line-height: 1.1; | |||||
| } | } | ||||
| .footer-inline-content strong, | .footer-inline-content strong, | ||||
| @@ -892,7 +892,7 @@ details p { | |||||
| .footer-badge-inline img { | .footer-badge-inline img { | ||||
| display: block; | display: block; | ||||
| width: 104px; | |||||
| width: 56px; | |||||
| height: auto; | height: auto; | ||||
| } | } | ||||
| @@ -900,21 +900,21 @@ details p { | |||||
| display: flex; | display: flex; | ||||
| justify-content: space-between; | justify-content: space-between; | ||||
| gap: 8px; | gap: 8px; | ||||
| padding-top: 3px; | |||||
| padding-top: 2px; | |||||
| border-top: 1px solid rgba(255,255,255,0.1); | border-top: 1px solid rgba(255,255,255,0.1); | ||||
| font-size: 0.52rem; | |||||
| line-height: 1.2; | |||||
| font-size: 0.46rem; | |||||
| line-height: 1.1; | |||||
| } | } | ||||
| @media (max-width: 820px) { | @media (max-width: 820px) { | ||||
| .footer-compact-row { | .footer-compact-row { | ||||
| grid-template-columns: 1fr; | grid-template-columns: 1fr; | ||||
| gap: 6px; | |||||
| gap: 4px; | |||||
| justify-items: start; | justify-items: start; | ||||
| } | } | ||||
| .footer-logo-inline img { | .footer-logo-inline img { | ||||
| width: 78px; | |||||
| width: 56px; | |||||
| } | } | ||||
| .footer-bottom { | .footer-bottom { | ||||
| @@ -1,4 +1,4 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <configuration> | <configuration> | ||||
| <appSettings> | <appSettings> | ||||
| @@ -112,4 +112,13 @@ | |||||
| </httpProtocol> | </httpProtocol> | ||||
| </system.webServer> | </system.webServer> | ||||
| <location path="css"> | |||||
| <system.webServer> | |||||
| <staticContent> | |||||
| <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="01:00:00" /> | |||||
| </staticContent> | |||||
| </system.webServer> | |||||
| </location> | |||||
| </configuration> | </configuration> | ||||
| @@ -1,616 +0,0 @@ | |||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8" /> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |||||
| <meta | |||||
| name="description" | |||||
| content="Order professional purple ballot envelope addressing, USPS barcode preparation, data processing, and mailing services." | |||||
| /> | |||||
| <title>Purple Envelope Addressing & Barcoding</title> | |||||
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |||||
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |||||
| <link | |||||
| href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" | |||||
| rel="stylesheet" | |||||
| /> | |||||
| <link rel="stylesheet" href="styles.css" /> | |||||
| </head> | |||||
| <body> | |||||
| <header class="site-header"> | |||||
| <div class="container nav-wrap"> | |||||
| <a class="brand" href="#top" aria-label="Purple Envelope home"> | |||||
| <span class="brand-mark" aria-hidden="true">PE</span> | |||||
| <span> | |||||
| <strong>Purple Envelope</strong> | |||||
| <small>Addressing & Barcoding</small> | |||||
| </span> | |||||
| </a> | |||||
| <nav class="main-nav" aria-label="Primary navigation"> | |||||
| <a href="#services">Services</a> | |||||
| <a href="#process">Process</a> | |||||
| <a href="#order">Order</a> | |||||
| <a href="#faq">FAQ</a> | |||||
| </nav> | |||||
| <a class="button button-small button-outline" href="#order">Start an order</a> | |||||
| </div> | |||||
| </header> | |||||
| <main id="top"> | |||||
| <section class="hero"> | |||||
| <div class="container hero-grid"> | |||||
| <div class="hero-copy"> | |||||
| <span class="eyebrow">Election mail production made simple</span> | |||||
| <h1>Accurate purple envelope addressing and USPS barcode preparation.</h1> | |||||
| <p class="hero-lead"> | |||||
| Upload your voter or recipient list, select your production options, | |||||
| and receive a clear order total before submitting your job. | |||||
| </p> | |||||
| <div class="hero-actions"> | |||||
| <a class="button button-primary" href="#order">Build your order</a> | |||||
| <a class="button button-secondary" href="#services">View services</a> | |||||
| </div> | |||||
| <ul class="trust-list" aria-label="Service benefits"> | |||||
| <li>Secure data handling</li> | |||||
| <li>Address formatting and validation</li> | |||||
| <li>Intelligent Mail barcode preparation</li> | |||||
| </ul> | |||||
| </div> | |||||
| <div class="hero-visual" aria-label="Sample addressed purple envelope"> | |||||
| <div class="envelope-card"> | |||||
| <div class="envelope-top"> | |||||
| <span class="sample-label">ADDRESS SERVICE REQUESTED</span> | |||||
| <span class="permit">PRESORTED<br>FIRST-CLASS MAIL</span> | |||||
| </div> | |||||
| <div class="recipient-block"> | |||||
| <strong>JANE SAMPLE</strong> | |||||
| <span>1234 EXAMPLE AVENUE</span> | |||||
| <span>GRAND RAPIDS MI 49503-1234</span> | |||||
| </div> | |||||
| <div class="barcode" aria-label="Sample barcode"> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| <span></span><span></span><span></span><span></span><span></span> | |||||
| </div> | |||||
| <div class="envelope-seal" aria-hidden="true"></div> | |||||
| </div> | |||||
| <div class="stat-card stat-card-one"> | |||||
| <strong>99.9%</strong> | |||||
| <span>production accuracy goal</span> | |||||
| </div> | |||||
| <div class="stat-card stat-card-two"> | |||||
| <strong>Fast</strong> | |||||
| <span>proof approval workflow</span> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| <section class="service-strip" aria-label="Service highlights"> | |||||
| <div class="container service-strip-grid"> | |||||
| <div> | |||||
| <strong>Address cleanup</strong> | |||||
| <span>Standardized mailing fields</span> | |||||
| </div> | |||||
| <div> | |||||
| <strong>USPS barcode setup</strong> | |||||
| <span>IMb-ready production data</span> | |||||
| </div> | |||||
| <div> | |||||
| <strong>Inkjet addressing</strong> | |||||
| <span>Clear, machine-readable output</span> | |||||
| </div> | |||||
| <div> | |||||
| <strong>Proof approval</strong> | |||||
| <span>Review before production begins</span> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| <section class="section" id="services"> | |||||
| <div class="container"> | |||||
| <div class="section-heading"> | |||||
| <span class="eyebrow">Complete production support</span> | |||||
| <h2>Everything needed to prepare your ballot envelopes.</h2> | |||||
| <p> | |||||
| Choose only the services you need, from basic addressing to a | |||||
| complete data-processing and mailing workflow. | |||||
| </p> | |||||
| </div> | |||||
| <div class="card-grid"> | |||||
| <article class="feature-card"> | |||||
| <span class="feature-number">01</span> | |||||
| <h3>Recipient addressing</h3> | |||||
| <p> | |||||
| Print recipient names, delivery addresses, city, state, ZIP Code, | |||||
| return-service endorsements, and optional identifiers. | |||||
| </p> | |||||
| </article> | |||||
| <article class="feature-card"> | |||||
| <span class="feature-number">02</span> | |||||
| <h3>Barcode preparation</h3> | |||||
| <p> | |||||
| Prepare and print Intelligent Mail barcode data using the mailing | |||||
| fields and identifiers supplied for your project. | |||||
| </p> | |||||
| </article> | |||||
| <article class="feature-card"> | |||||
| <span class="feature-number">03</span> | |||||
| <h3>List processing</h3> | |||||
| <p> | |||||
| Review column structure, remove obvious formatting problems, | |||||
| standardize capitalization, and identify incomplete records. | |||||
| </p> | |||||
| </article> | |||||
| <article class="feature-card"> | |||||
| <span class="feature-number">04</span> | |||||
| <h3>Proof and production</h3> | |||||
| <p> | |||||
| Receive a digital sample for approval before the full envelope | |||||
| run enters production. | |||||
| </p> | |||||
| </article> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| <section class="section section-muted" id="process"> | |||||
| <div class="container"> | |||||
| <div class="section-heading centered"> | |||||
| <span class="eyebrow">Simple four-step workflow</span> | |||||
| <h2>From uploaded list to completed order.</h2> | |||||
| </div> | |||||
| <div class="process-grid"> | |||||
| <div class="process-step"> | |||||
| <span>1</span> | |||||
| <h3>Enter job details</h3> | |||||
| <p>Provide quantity, envelope specifications, and due date.</p> | |||||
| </div> | |||||
| <div class="process-step"> | |||||
| <span>2</span> | |||||
| <h3>Upload your files</h3> | |||||
| <p>Add recipient data, artwork, instructions, or a sample envelope.</p> | |||||
| </div> | |||||
| <div class="process-step"> | |||||
| <span>3</span> | |||||
| <h3>Approve your proof</h3> | |||||
| <p>Review the address placement and sample barcode before printing.</p> | |||||
| </div> | |||||
| <div class="process-step"> | |||||
| <span>4</span> | |||||
| <h3>Production begins</h3> | |||||
| <p>Your approved job is addressed, checked, packed, and delivered.</p> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| <section class="section order-section" id="order"> | |||||
| <div class="container"> | |||||
| <div class="section-heading"> | |||||
| <span class="eyebrow">Online order request</span> | |||||
| <h2>Build your envelope addressing order.</h2> | |||||
| <p> | |||||
| This form calculates an estimate. Final pricing can be confirmed | |||||
| after your data and production requirements are reviewed. | |||||
| </p> | |||||
| </div> | |||||
| <form class="order-layout" id="orderForm"> | |||||
| <div class="form-panel"> | |||||
| <fieldset class="form-section"> | |||||
| <legend> | |||||
| <span>1</span> | |||||
| Contact information | |||||
| </legend> | |||||
| <div class="form-grid two-columns"> | |||||
| <label> | |||||
| Full name | |||||
| <input type="text" name="fullName" autocomplete="name" required /> | |||||
| </label> | |||||
| <label> | |||||
| Organization | |||||
| <input type="text" name="organization" autocomplete="organization" /> | |||||
| </label> | |||||
| <label> | |||||
| Email address | |||||
| <input type="email" name="email" autocomplete="email" required /> | |||||
| </label> | |||||
| <label> | |||||
| Phone number | |||||
| <input type="tel" name="phone" autocomplete="tel" required /> | |||||
| </label> | |||||
| </div> | |||||
| </fieldset> | |||||
| <fieldset class="form-section"> | |||||
| <legend> | |||||
| <span>2</span> | |||||
| Job specifications | |||||
| </legend> | |||||
| <div class="form-grid two-columns"> | |||||
| <label> | |||||
| Number of envelopes | |||||
| <input | |||||
| id="quantity" | |||||
| type="number" | |||||
| name="quantity" | |||||
| min="100" | |||||
| step="50" | |||||
| value="1000" | |||||
| required | |||||
| /> | |||||
| <small>Minimum order: 100 envelopes</small> | |||||
| </label> | |||||
| <label> | |||||
| Requested completion date | |||||
| <input type="date" name="dueDate" required /> | |||||
| </label> | |||||
| <label> | |||||
| Envelope size | |||||
| <select name="envelopeSize" id="envelopeSize"> | |||||
| <option value="standard">Standard ballot envelope</option> | |||||
| <option value="large">Large ballot envelope</option> | |||||
| <option value="custom">Custom size</option> | |||||
| </select> | |||||
| </label> | |||||
| <label> | |||||
| Envelope supply | |||||
| <select name="envelopeSupply" id="envelopeSupply"> | |||||
| <option value="customer">I will supply the envelopes</option> | |||||
| <option value="vendor">Please supply the envelopes</option> | |||||
| </select> | |||||
| </label> | |||||
| <label> | |||||
| Turnaround | |||||
| <select name="turnaround" id="turnaround"> | |||||
| <option value="standard">Standard — 5 to 7 business days</option> | |||||
| <option value="rush">Rush — 2 to 3 business days</option> | |||||
| </select> | |||||
| </label> | |||||
| <label> | |||||
| Ink color | |||||
| <select name="inkColor"> | |||||
| <option>Black</option> | |||||
| <option>Blue</option> | |||||
| <option>Custom color review</option> | |||||
| </select> | |||||
| </label> | |||||
| </div> | |||||
| </fieldset> | |||||
| <fieldset class="form-section"> | |||||
| <legend> | |||||
| <span>3</span> | |||||
| Services | |||||
| </legend> | |||||
| <div class="option-grid"> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="addressing" | |||||
| data-label="Envelope addressing" | |||||
| data-rate="0.12" | |||||
| checked | |||||
| /> | |||||
| <span> | |||||
| <strong>Envelope addressing</strong> | |||||
| <small>Recipient name and mailing address</small> | |||||
| </span> | |||||
| <b>$0.12 each</b> | |||||
| </label> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="barcode" | |||||
| data-label="IMb barcode preparation" | |||||
| data-rate="0.045" | |||||
| checked | |||||
| /> | |||||
| <span> | |||||
| <strong>IMb barcode preparation</strong> | |||||
| <small>Barcode data setup and printing</small> | |||||
| </span> | |||||
| <b>$0.045 each</b> | |||||
| </label> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="cleanup" | |||||
| data-label="Data cleanup" | |||||
| data-flat="75" | |||||
| /> | |||||
| <span> | |||||
| <strong>Data cleanup</strong> | |||||
| <small>Formatting review and exception report</small> | |||||
| </span> | |||||
| <b>$75 flat</b> | |||||
| </label> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="casing" | |||||
| data-label="Address casing" | |||||
| data-rate="0.025" | |||||
| /> | |||||
| <span> | |||||
| <strong>Insert and seal</strong> | |||||
| <small>Insert supplied material and seal envelopes</small> | |||||
| </span> | |||||
| <b>$0.025 each</b> | |||||
| </label> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="proof" | |||||
| data-label="Printed production proof" | |||||
| data-flat="35" | |||||
| /> | |||||
| <span> | |||||
| <strong>Printed production proof</strong> | |||||
| <small>Physical proof shipped before production</small> | |||||
| </span> | |||||
| <b>$35 flat</b> | |||||
| </label> | |||||
| <label class="option-card"> | |||||
| <input | |||||
| type="checkbox" | |||||
| name="services" | |||||
| value="delivery" | |||||
| data-label="Local delivery" | |||||
| data-flat="45" | |||||
| /> | |||||
| <span> | |||||
| <strong>Local delivery</strong> | |||||
| <small>Delivery within the designated service area</small> | |||||
| </span> | |||||
| <b>$45 flat</b> | |||||
| </label> | |||||
| </div> | |||||
| </fieldset> | |||||
| <fieldset class="form-section"> | |||||
| <legend> | |||||
| <span>4</span> | |||||
| Files and instructions | |||||
| </legend> | |||||
| <div class="upload-grid"> | |||||
| <label class="upload-box"> | |||||
| <input type="file" name="dataFile" accept=".csv,.xlsx,.xls,.txt" /> | |||||
| <span class="upload-icon" aria-hidden="true">↑</span> | |||||
| <strong>Recipient data file</strong> | |||||
| <small>CSV, Excel, or tab-delimited text</small> | |||||
| </label> | |||||
| <label class="upload-box"> | |||||
| <input type="file" name="artworkFile" accept=".pdf,.jpg,.jpeg,.png" /> | |||||
| <span class="upload-icon" aria-hidden="true">↑</span> | |||||
| <strong>Artwork or sample</strong> | |||||
| <small>PDF, JPG, or PNG</small> | |||||
| </label> | |||||
| </div> | |||||
| <label class="full-width"> | |||||
| Special instructions | |||||
| <textarea | |||||
| name="instructions" | |||||
| rows="6" | |||||
| placeholder="Describe placement requirements, barcode fields, endorsements, sorting, packing, delivery, or other instructions." | |||||
| ></textarea> | |||||
| </label> | |||||
| <label class="agreement"> | |||||
| <input type="checkbox" required /> | |||||
| <span> | |||||
| I confirm that I am authorized to submit this data and request | |||||
| production services for this mailing. | |||||
| </span> | |||||
| </label> | |||||
| </fieldset> | |||||
| </div> | |||||
| <aside class="summary-panel" aria-label="Order estimate"> | |||||
| <div class="summary-sticky"> | |||||
| <span class="eyebrow">Estimated order</span> | |||||
| <h3>Order summary</h3> | |||||
| <div class="summary-line"> | |||||
| <span>Quantity</span> | |||||
| <strong id="summaryQuantity">1,000</strong> | |||||
| </div> | |||||
| <div id="serviceSummary" class="service-summary"></div> | |||||
| <div class="summary-line"> | |||||
| <span>Setup fee</span> | |||||
| <strong id="setupFee">$45.00</strong> | |||||
| </div> | |||||
| <div class="summary-line rush-line" id="rushLine" hidden> | |||||
| <span>Rush surcharge</span> | |||||
| <strong id="rushFee">$0.00</strong> | |||||
| </div> | |||||
| <div class="summary-total"> | |||||
| <span>Estimated total</span> | |||||
| <strong id="estimatedTotal">$210.00</strong> | |||||
| </div> | |||||
| <p class="summary-note"> | |||||
| Postage, taxes, shipping, envelope stock, and unusual data work | |||||
| are not included unless selected or quoted separately. | |||||
| </p> | |||||
| <button class="button button-primary button-full" type="submit"> | |||||
| Submit order request | |||||
| </button> | |||||
| <p class="secure-note"> | |||||
| Your data should be transmitted using your organization’s | |||||
| approved secure process before production. | |||||
| </p> | |||||
| </div> | |||||
| </aside> | |||||
| </form> | |||||
| </div> | |||||
| </section> | |||||
| <section class="section section-muted" id="faq"> | |||||
| <div class="container faq-layout"> | |||||
| <div class="section-heading"> | |||||
| <span class="eyebrow">Frequently asked questions</span> | |||||
| <h2>Ordering and production details.</h2> | |||||
| </div> | |||||
| <div class="faq-list"> | |||||
| <details open> | |||||
| <summary>What file formats can I upload?</summary> | |||||
| <p> | |||||
| CSV and Excel files are preferred. Clearly labeled columns should | |||||
| include the recipient name, delivery address, city, state, ZIP | |||||
| Code, and any barcode-related fields required for the project. | |||||
| </p> | |||||
| </details> | |||||
| <details> | |||||
| <summary>Will I receive a proof?</summary> | |||||
| <p> | |||||
| Yes. A digital proof can be provided before production. A printed | |||||
| physical proof can also be selected as an additional service. | |||||
| </p> | |||||
| </details> | |||||
| <details> | |||||
| <summary>Does the estimate include postage?</summary> | |||||
| <p> | |||||
| No. Postage depends on the mailing class, presort results, entry | |||||
| location, and current postal requirements. It should be quoted | |||||
| separately after the mailing data is reviewed. | |||||
| </p> | |||||
| </details> | |||||
| <details> | |||||
| <summary>Can you supply the purple envelopes?</summary> | |||||
| <p> | |||||
| Yes. Select “Please supply the envelopes” in the order form. | |||||
| Stock, color, size, window configuration, and availability will | |||||
| be confirmed before production. | |||||
| </p> | |||||
| </details> | |||||
| </div> | |||||
| </div> | |||||
| </section> | |||||
| <section class="cta-section"> | |||||
| <div class="container cta-card"> | |||||
| <div> | |||||
| <span class="eyebrow">Need a custom production quote?</span> | |||||
| <h2>Large quantities and special ballot configurations are welcome.</h2> | |||||
| </div> | |||||
| <a class="button button-light" href="#order">Request pricing</a> | |||||
| </div> | |||||
| </section> | |||||
| </main> | |||||
| <footer class="site-footer"> | |||||
| <div class="container footer-grid"> | |||||
| <div> | |||||
| <a class="brand footer-brand" href="#top"> | |||||
| <span class="brand-mark" aria-hidden="true">PE</span> | |||||
| <span> | |||||
| <strong>Purple Envelope</strong> | |||||
| <small>Addressing & Barcoding</small> | |||||
| </span> | |||||
| </a> | |||||
| <p> | |||||
| Professional envelope data preparation, addressing, barcode printing, | |||||
| proofing, and production support. | |||||
| </p> | |||||
| </div> | |||||
| <div> | |||||
| <strong>Services</strong> | |||||
| <a href="#services">Addressing</a> | |||||
| <a href="#services">Barcoding</a> | |||||
| <a href="#services">Data processing</a> | |||||
| </div> | |||||
| <div> | |||||
| <strong>Ordering</strong> | |||||
| <a href="#process">How it works</a> | |||||
| <a href="#order">Get an estimate</a> | |||||
| <a href="#faq">FAQ</a> | |||||
| </div> | |||||
| <div> | |||||
| <strong>Contact</strong> | |||||
| <a href="mailto:orders@example.com">orders@example.com</a> | |||||
| <a href="tel:+15555550100">(555) 555-0100</a> | |||||
| <span>Monday–Friday, 8:00 AM–5:00 PM</span> | |||||
| </div> | |||||
| </div> | |||||
| <div class="container footer-bottom"> | |||||
| <span>© <span id="year"></span> Purple Envelope Services.</span> | |||||
| <span>Privacy · Terms · Data security</span> | |||||
| </div> | |||||
| </footer> | |||||
| <dialog id="confirmationDialog"> | |||||
| <div class="dialog-content"> | |||||
| <button class="dialog-close" type="button" aria-label="Close">×</button> | |||||
| <span class="dialog-icon" aria-hidden="true">✓</span> | |||||
| <h2>Order request ready</h2> | |||||
| <p> | |||||
| The front-end form is working. Connect this form to your server-side | |||||
| order endpoint, payment system, CRM, or email workflow to process live orders. | |||||
| </p> | |||||
| <button class="button button-primary dialog-button" type="button">Close</button> | |||||
| </div> | |||||
| </dialog> | |||||
| <script src="script.js"></script> | |||||
| </body> | |||||
| </html> | |||||
| @@ -1,121 +0,0 @@ | |||||
| const form = document.getElementById("orderForm"); | |||||
| const quantityInput = document.getElementById("quantity"); | |||||
| const turnaround = document.getElementById("turnaround"); | |||||
| const envelopeSupply = document.getElementById("envelopeSupply"); | |||||
| const serviceInputs = [...document.querySelectorAll('input[name="services"]')]; | |||||
| const summaryQuantity = document.getElementById("summaryQuantity"); | |||||
| const serviceSummary = document.getElementById("serviceSummary"); | |||||
| const setupFeeElement = document.getElementById("setupFee"); | |||||
| const rushLine = document.getElementById("rushLine"); | |||||
| const rushFeeElement = document.getElementById("rushFee"); | |||||
| const estimatedTotal = document.getElementById("estimatedTotal"); | |||||
| const dialog = document.getElementById("confirmationDialog"); | |||||
| const currency = new Intl.NumberFormat("en-US", { | |||||
| style: "currency", | |||||
| currency: "USD" | |||||
| }); | |||||
| const number = new Intl.NumberFormat("en-US"); | |||||
| function calculateOrder() { | |||||
| const quantity = Math.max(Number(quantityInput.value) || 0, 0); | |||||
| const setupFee = quantity > 0 ? 45 : 0; | |||||
| let subtotal = setupFee; | |||||
| const lines = []; | |||||
| serviceInputs.forEach((input) => { | |||||
| if (!input.checked) return; | |||||
| const rate = Number(input.dataset.rate || 0); | |||||
| const flat = Number(input.dataset.flat || 0); | |||||
| const amount = flat || quantity * rate; | |||||
| subtotal += amount; | |||||
| lines.push({ | |||||
| label: input.dataset.label, | |||||
| amount | |||||
| }); | |||||
| }); | |||||
| if (envelopeSupply.value === "vendor") { | |||||
| const envelopeCost = quantity * 0.18; | |||||
| subtotal += envelopeCost; | |||||
| lines.push({ | |||||
| label: "Purple envelope stock", | |||||
| amount: envelopeCost | |||||
| }); | |||||
| } | |||||
| const rushFee = turnaround.value === "rush" ? subtotal * 0.25 : 0; | |||||
| const total = subtotal + rushFee; | |||||
| summaryQuantity.textContent = number.format(quantity); | |||||
| setupFeeElement.textContent = currency.format(setupFee); | |||||
| rushFeeElement.textContent = currency.format(rushFee); | |||||
| estimatedTotal.textContent = currency.format(total); | |||||
| rushLine.hidden = turnaround.value !== "rush"; | |||||
| serviceSummary.innerHTML = lines | |||||
| .map( | |||||
| (line) => ` | |||||
| <div class="summary-line"> | |||||
| <span>${line.label}</span> | |||||
| <strong>${currency.format(line.amount)}</strong> | |||||
| </div> | |||||
| ` | |||||
| ) | |||||
| .join(""); | |||||
| } | |||||
| function showSelectedFile(input) { | |||||
| const uploadBox = input.closest(".upload-box"); | |||||
| const strong = uploadBox.querySelector("strong"); | |||||
| const small = uploadBox.querySelector("small"); | |||||
| if (input.files.length) { | |||||
| strong.textContent = input.files[0].name; | |||||
| small.textContent = "File selected"; | |||||
| } | |||||
| } | |||||
| quantityInput.addEventListener("input", calculateOrder); | |||||
| turnaround.addEventListener("change", calculateOrder); | |||||
| envelopeSupply.addEventListener("change", calculateOrder); | |||||
| serviceInputs.forEach((input) => input.addEventListener("change", calculateOrder)); | |||||
| document.querySelectorAll('input[type="file"]').forEach((input) => { | |||||
| input.addEventListener("change", () => showSelectedFile(input)); | |||||
| }); | |||||
| form.addEventListener("submit", (event) => { | |||||
| event.preventDefault(); | |||||
| if (!form.checkValidity()) { | |||||
| form.reportValidity(); | |||||
| return; | |||||
| } | |||||
| dialog.showModal(); | |||||
| }); | |||||
| dialog.querySelectorAll(".dialog-close, .dialog-button").forEach((button) => { | |||||
| button.addEventListener("click", () => dialog.close()); | |||||
| }); | |||||
| dialog.addEventListener("click", (event) => { | |||||
| const bounds = dialog.getBoundingClientRect(); | |||||
| const clickedOutside = | |||||
| event.clientX < bounds.left || | |||||
| event.clientX > bounds.right || | |||||
| event.clientY < bounds.top || | |||||
| event.clientY > bounds.bottom; | |||||
| if (clickedOutside) dialog.close(); | |||||
| }); | |||||
| document.getElementById("year").textContent = new Date().getFullYear(); | |||||
| calculateOrder(); | |||||
Powered by TurnKey Linux.