瀏覽代碼

Add revised order-form fields, boxed request-order layout, branded emails

- 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
Daniel Covington 3 天之前
父節點
當前提交
b7575e3995
共有 18 個檔案被更改,包括 188 行新增1975 行删除
  1. +1
    -0
      app/controllers/OrderApiController.asp
  2. +1
    -1
      app/controllers/RequestOrderController.asp
  3. +3
    -1
      app/models/OrderDetailsAnswersMapper.asp
  4. +1
    -0
      app/models/OrderDetailsLabels.asp
  5. +11
    -11
      app/models/OrderDetailsRepository.asp
  6. +39
    -26
      app/models/OrderMailer.asp
  7. +23
    -4
      app/models/POBO_OrderDetails.asp
  8. +17
    -9
      app/views/Order/continue.asp
  9. +25
    -23
      app/views/RequestOrder/index.asp
  10. +44
    -0
      db/migrations/20260820114612_add_purple_ballot_list_count_and_blue_nonprofit_options.asp
  11. 二進制
      docs/lucid/Election Materials Order Form #1 (2).pdf
  12. 二進制
      docs/lucid/Election Materials Request Form #2 (2).pdf
  13. +13
    -13
      public/css/styles.css
  14. +10
    -1
      public/web.config
  15. 二進制
      purple-envelope-order-site.zip
  16. +0
    -616
      purple-envelope-order-site/index.html
  17. +0
    -121
      purple-envelope-order-site/script.js
  18. +0
    -1149
      purple-envelope-order-site/styles.css

+ 1
- 0
app/controllers/OrderApiController.asp 查看文件

@@ -119,6 +119,7 @@ Class OrderApiController_Class
' should not turn a successful submission into a failed one.
OrderMailer().SendOrderDetailsEmail order, model

Flash().Success = "Your order has been submitted. We'll be in touch with your quote soon."
Response.Write "{""success"":true}"
End Sub



+ 1
- 1
app/controllers/RequestOrderController.asp 查看文件

@@ -93,7 +93,7 @@ Class RequestOrderController_Class
OrderMailer().SendContinuationEmail email, token

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 Class


+ 3
- 1
app/models/OrderDetailsAnswersMapper.asp 查看文件

@@ -19,6 +19,7 @@ Function ValidateOrderDetailsAnswers(answers)
RequireEnum errors, answers, "PurplePrintStyle", Array("ColorCoding", "BlackOnly")
RequireEnum errors, answers, "PurpleColorCodingBy", Array("Precinct", "Election")
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, "MailingHasNonprofitStatus", Array("Yes", "No", "NotSure")

@@ -27,9 +28,9 @@ Function ValidateOrderDetailsAnswers(answers)
RequireBoolean errors, answers, "PurpleTrackMIBallot"
RequireBoolean errors, answers, "BlueWantsQuote"
RequireBoolean errors, answers, "BluePrintPermit"
RequireBoolean errors, answers, "BlueHasNonprofitStatus"
RequireBoolean errors, answers, "MailingWantsService"

RequireNonNegativeInteger errors, answers, "PurplePermanentBallotListCount"
RequireNonNegativeInteger errors, answers, "PurplePrecinctCount"
RequireNonNegativeInteger errors, answers, "PurpleExtraEnvelopeQty"
RequireNonNegativeInteger errors, answers, "MailingEstimatedQuantity"
@@ -65,6 +66,7 @@ Sub MapOrderDetailsAnswers(ByRef model, answers)
model.PurpleColorNames = DictValue(answers, "PurpleColorNames")
model.PurpleTrackMIBallot = DictValue(answers, "PurpleTrackMIBallot")
model.PurpleExtraEnvelopeQty = DictValue(answers, "PurpleExtraEnvelopeQty")
model.PurplePermanentBallotListCount = DictValue(answers, "PurplePermanentBallotListCount")

' Blue envelope
model.BlueWantsQuote = DictValue(answers, "BlueWantsQuote")


+ 1
- 0
app/models/OrderDetailsLabels.asp 查看文件

@@ -69,6 +69,7 @@ Function LabelNonprofitStatus(code)
Case "Yes" : LabelNonprofitStatus = "Yes"
Case "No" : LabelNonprofitStatus = "No"
Case "NotSure" : LabelNonprofitStatus = "I'm not sure"
Case "Unsure" : LabelNonprofitStatus = "Unsure (that's okay, we can help)"
Case Else : LabelNonprofitStatus = CStr(code & "")
End Select
End Function


+ 11
- 11
app/models/OrderDetailsRepository.asp 查看文件

@@ -1,6 +1,6 @@
<%
' 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
'
' Dependencies:
@@ -13,7 +13,7 @@
Class OrderDetailsRepository_Class

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))
If rs.EOF Then
Err.Raise 1, "OrderDetailsRepository_Class", RecordNotFoundException("OrderDetailID", id)
@@ -28,7 +28,7 @@ Class OrderDetailsRepository_Class
End Function

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
If Not IsEmpty(where_kvarray) Then
KVUnzip where_kvarray, where_keys, where_values
@@ -52,7 +52,7 @@ Class OrderDetailsRepository_Class
End Function

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
If Not IsEmpty(where_kvarray) Then
KVUnzip where_kvarray, where_keys, where_values
@@ -77,7 +77,7 @@ Class OrderDetailsRepository_Class
End Function

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()
If IsArray(columns_array) And UBound(columns_array) >= 0 Then
sql = sql & " WHERE "
@@ -112,9 +112,9 @@ Class OrderDetailsRepository_Class
End Function

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
On Error Resume Next
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)
End If
On Error GoTo 0
If Not rsId.EOF Then
If Not IsNull(rsId(0)) Then model.OrderDetailID = rsId(0)
End If
@@ -132,8 +132,8 @@ Class OrderDetailsRepository_Class
End Sub

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

Public Sub Delete(id)


+ 39
- 26
app/models/OrderMailer.asp 查看文件

@@ -9,13 +9,18 @@ Class OrderMailer_Class
Dim continueUrl
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()
mail.Subject = "Continue your Purple Envelope order"
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.Send
End Sub
@@ -55,14 +60,7 @@ Class OrderMailer_Class

Private Function BuildOrderDetailsEmailBody(order, model)
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>" & _
"<p style=""color:#6d6574; font-size:13px; margin:0 0 8px;"">Jurisdiction number <strong style=""color:#201a27;"">" & H(order("JurisdictionNumber")) & "</strong>" & _
" &middot; submitted " & H(EmailDate(model.SubmittedAt)) & "</p>"
@@ -80,6 +78,7 @@ Class OrderMailer_Class
End If

html = html & EmailSection("Purple Ballot Envelopes", _
EmailRow("Permanent ballot list count", model.PurplePermanentBallotListCount) & _
EmailRow("Envelope stock", LabelEnvelopeStock(model.PurpleEnvelopeStock)) & _
EmailRow("Print option", LabelPrintOption(model.PurplePrintOption)) & _
EmailRow("Also wants KCI blue envelopes", EmailYesNo(model.PurpleWantsBlueEnvelopes)) & _
@@ -93,24 +92,25 @@ Class OrderMailer_Class
End If

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
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
bluePermitLine = EmailRow("City of permit holder", model.BluePermitCity) & _
bluePermitLine = bluePermitLine & _
EmailRow("City of permit holder", model.BluePermitCity) & _
EmailRow("Permit number", model.BluePermitNumber)
If IsTrue(model.BlueHasNonprofitStatus) Then
bluePermitLine = bluePermitLine & EmailRow("Nonprofit authorization code", model.BlueNonprofitAuthCode)
End If
End If

html = html & EmailSection("Blue AV Envelopes", _
EmailRow("Print permit", EmailYesNo(model.BluePrintPermit)) & _
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)
End If

@@ -130,14 +130,27 @@ Class OrderMailer_Class
EmailRow("Special requests", model.SpecialRequests))
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>" & _
"<tr><td style=""background:#f0e7f8; padding:16px 32px; text-align:center;"">" & _
"<span style=""color:#6529a0; font-size:11px;"">Purple Envelope &middot; Addressing &amp; 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 &middot; info@kentcommunications.com</div>" & _
"</td></tr>" & _
"</table></td></tr></table></body></html>"

BuildOrderDetailsEmailBody = html
End Function

Private Function EmailSection(sectionTitle, rowsHtml)


+ 23
- 4
app/models/POBO_OrderDetails.asp 查看文件

@@ -42,6 +42,7 @@ Class POBO_OrderDetails
Private pPurpleEnvelopeProvider
Private pPurpleEnvelopeStock
Private pPurpleExtraEnvelopeQty
Private pPurplePermanentBallotListCount
Private pPurplePrecinctCount
Private pPurplePrintOption
Private pPurplePrintStyle
@@ -85,6 +86,7 @@ Class POBO_OrderDetails
pPurpleEnvelopeProvider = Null
pPurpleEnvelopeStock = Null
pPurpleExtraEnvelopeQty = 0
pPurplePermanentBallotListCount = 0
pPurplePrecinctCount = 0
pPurplePrintOption = Null
pPurplePrintStyle = Null
@@ -94,7 +96,7 @@ Class POBO_OrderDetails
pSecrecySleevesQty = 0
pSpecialRequests = Null
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

Public Property Get PrimaryKey()
@@ -145,10 +147,10 @@ Class POBO_OrderDetails

Public Property Let BlueHasNonprofitStatus(val)
On Error Resume Next
If IsNull(val) Then
pBlueHasNonprofitStatus = Null
If IsNumeric(val) Then
pBlueHasNonprofitStatus = CDbl(val)
Else
pBlueHasNonprofitStatus = CBool(val)
pBlueHasNonprofitStatus = val
End If
If Err.Number <> 0 Then
Err.Raise Err.Number, "POBO_OrderDetails.BlueHasNonprofitStatus", "Invalid value for BlueHasNonprofitStatus: " & Err.Description
@@ -637,6 +639,23 @@ Class POBO_OrderDetails
On Error GoTo 0
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()
PurplePrecinctCount = pPurplePrecinctCount
End Property


+ 17
- 9
app/views/Order/continue.asp 查看文件

@@ -96,6 +96,13 @@
title: "Would you like for KCI to print your purple AV envelopes?",
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",
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>"
},
{
type: "boolean", name: "BlueHasNonprofitStatus", labelTrue: "Yes", labelFalse: "No",
type: "radiogroup", name: "BlueHasNonprofitStatus",
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",
title: "Please provide your nonprofit authorization code.",
visibleIf: "{BlueHasNonprofitStatus} = true",
visibleIf: "{BlueHasNonprofitStatus} = 'Yes'",
isRequired: true
},
{
@@ -442,12 +454,8 @@
});
})
.then(function (result) {
var container = document.getElementById("surveyContainer");
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 {
alert((result.body && result.body.error) || "Something went wrong submitting your order. Please try again.");
}


+ 25
- 23
app/views/RequestOrder/index.asp 查看文件

@@ -1,32 +1,34 @@
<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>

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>

+ 44
- 0
db/migrations/20260820114612_add_purple_ballot_list_count_and_blue_nonprofit_options.asp 查看文件

@@ -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
%>

二進制
docs/lucid/Election Materials Order Form #1 (2).pdf 查看文件


二進制
docs/lucid/Election Materials Request Form #2 (2).pdf 查看文件


+ 13
- 13
public/css/styles.css 查看文件

@@ -847,7 +847,7 @@ details p {
}
.site-footer {
flex: 0 0 auto;
padding: 6px 0 4px;
padding: 3px 0 2px;
color: rgba(255,255,255,0.84);
background: #254B74;
border-top: 1px solid rgba(255, 255, 255, 0.12);
@@ -855,19 +855,19 @@ details p {
.footer-shell {
display: flex;
flex-direction: column;
gap: 4px;
gap: 2px;
}

.footer-compact-row {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
gap: 10px;
gap: 6px;
}

.footer-logo-inline img {
display: block;
width: 104px;
width: 56px;
height: auto;
}

@@ -875,9 +875,9 @@ details p {
display: flex;
align-items: center;
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,
@@ -892,7 +892,7 @@ details p {

.footer-badge-inline img {
display: block;
width: 104px;
width: 56px;
height: auto;
}

@@ -900,21 +900,21 @@ details p {
display: flex;
justify-content: space-between;
gap: 8px;
padding-top: 3px;
padding-top: 2px;
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) {
.footer-compact-row {
grid-template-columns: 1fr;
gap: 6px;
gap: 4px;
justify-items: start;
}

.footer-logo-inline img {
width: 78px;
width: 56px;
}

.footer-bottom {


+ 10
- 1
public/web.config 查看文件

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

<appSettings>
@@ -112,4 +112,13 @@
</httpProtocol>
</system.webServer>


<location path="css">
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="01:00:00" />
</staticContent>
</system.webServer>
</location>
</configuration>


二進制
purple-envelope-order-site.zip 查看文件


+ 0
- 616
purple-envelope-order-site/index.html 查看文件

@@ -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>&copy; <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">&times;</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>

+ 0
- 121
purple-envelope-order-site/script.js 查看文件

@@ -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();

+ 0
- 1149
purple-envelope-order-site/styles.css
文件差異過大導致無法顯示
查看文件


Loading…
取消
儲存

Powered by TurnKey Linux.