From d62714e68da4a6be94ae29f49732c047a7ff3488 Mon Sep 17 00:00:00 2001 From: juchechu Date: Mon, 17 Aug 2026 22:34:53 +0100 Subject: [PATCH 1/2] feat: email preview preheader + purple accent polish + AI chat focus trap fix - Add PreviewTitle + PreheaderText to all 3 email data structs so email clients show descriptive preview text instead of the tagline - Redesign Best Practices email: purple left accents on header/cards/links, purple stat numbers, hero badge, shadow/glow CTA button, 3D bottom border - Apply matching purple accents to Password Reset + Problem Reminder - Fix AnalysisModal focus trap: pass modal={!chatOpen} to Radix Dialog so FollowUpDrawer textarea can receive keyboard focus --- .../best-practices/AnalysisModal.tsx | 2 +- internal/api/admin.go | 4 + internal/api/password_reset.go | 2 + internal/email/email.go | 109 ++++++++++-------- internal/email/email_test.go | 37 +++++- internal/email/problem_reminder_test.go | 10 ++ 6 files changed, 111 insertions(+), 53 deletions(-) diff --git a/frontend/components/best-practices/AnalysisModal.tsx b/frontend/components/best-practices/AnalysisModal.tsx index 6a678add..164145f9 100644 --- a/frontend/components/best-practices/AnalysisModal.tsx +++ b/frontend/components/best-practices/AnalysisModal.tsx @@ -263,7 +263,7 @@ export function AnalysisModal({ return ( <> - + override for email client preview + PreheaderText string // hidden preheader text shown before email is opened } // RenderPasswordReset renders the password-reset email into w. @@ -104,6 +111,8 @@ type ProblemReminderData struct { SupportEmail string Tagline string Year int + PreviewTitle string + PreheaderText string } // RenderProblemReminder renders a problem reminder email into w. @@ -255,6 +264,8 @@ type BestPracticesData struct { BestRuntimeMs int DeveloperCount int TopSolutions []DigestSolution + PreviewTitle string + PreheaderText string } // RenderBestPractices renders the Best Practices announcement email into w. @@ -298,7 +309,7 @@ func bestPracticesBody() string { - +
@@ -324,11 +335,13 @@ func bestPracticesBody() string {
-
+
-

Introducing Best Practices

+

Discover Best Practices

-

+

+ +

See how top developers solve real problems. Browse community solutions, get AI-powered code analysis, and learn from the best.

@@ -338,26 +351,26 @@ See how top developers solve real problems. Browse community solutions, get AI-p
- +
@@ -368,16 +381,16 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
{{.SolutionCount}}
+
{{.SolutionCount}}
Solutions
-
{{.DeveloperCount}}
+
{{.DeveloperCount}}
Developers
-
{{.TotalLikes}}
+
{{.TotalLikes}}
Likes
-
{{.GoCount}}
+
{{.GoCount}}
Go
-
{{.PythonCount}}
+
{{.PythonCount}}
Python
- +
@@ -391,16 +404,16 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
+
-
Community Solutions
+
Community Solutions

Compare how others approached the same problem. Sort by most liked, fastest runtime, or newest submissions. Every solution includes the full source code and developer stats.

- +
@@ -414,16 +427,16 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
+
-
AI-Powered Code Analysis
+
AI-Powered Code Analysis

Click any solution to get instant AI analysis: quality scores, efficiency and readability ratings, time and space complexity breakdown, key techniques, strengths, and areas for improvement. Ask follow-up questions about the approach.

- +
@@ -438,16 +451,16 @@ See how top developers solve real problems. Browse community solutions, get AI-p {{if .TopSolutions}}
-
+
-
How to Get Featured
+
How to Get Featured

Solve any problem to submit your solution. Other developers can like your code, and the top-rated solutions appear at the top of Best Practices. The more problems you solve, the more your solutions get discovered.

-
Top Rated Solutions
+
Top Rated Solutions
{{range $i, $s := .TopSolutions}} -
+ - @@ -511,7 +524,7 @@ func problemReminderBody() string { -
{{.ProblemTitle}}
-
by {{.UserName}} · {{.Language}}
+
by {{.UserName}} · {{.Language}}
@@ -470,8 +483,8 @@ See how top developers solve real problems. Browse community solutions, get AI-p -
-Explore Best Practices + +Explore Best Practices
@@ -482,20 +495,20 @@ See how top developers solve real problems. Browse community solutions, get AI-p
-
+
Button not working?
- +
+ -
{{.PlatformName}}
+
{{.PlatformName}}
{{.Tagline}}
- +
© {{.Year}} {{.PlatformName}}. All rights reserved.
+
@@ -550,13 +563,13 @@ Sharpen your skills with this short exercise: - +
@@ -568,20 +581,20 @@ Sharpen your skills with this short exercise: -
+
Button not working?
- +
- @@ -613,10 +626,10 @@ const layoutBase = `{{define "layoutBase"}} -{{.PlatformName}} +{{if .PreviewTitle}}{{.PreviewTitle}}{{else}}{{.PlatformName}}{{end}} - +{{if .PreheaderText}}
{{.PreheaderText}}
{{end}}
-
{{.ProblemTitle}}
+
{{.ProblemTitle}}
{{.ProblemExcerptHTML}}
+ -
{{.PlatformName}}
+
{{.PlatformName}}
{{.Tagline}}
- +
© {{.Year}} {{.PlatformName}}. All rights reserved.
-
@@ -638,7 +651,7 @@ const passwordResetBody = `{{define "content"}}
+ @@ -734,11 +747,11 @@ This secure link expires in {{.Expire - diff --git a/internal/email/email_test.go b/internal/email/email_test.go index 33d0c51c..61c74e94 100644 --- a/internal/email/email_test.go +++ b/internal/email/email_test.go @@ -14,6 +14,8 @@ func testData() PasswordResetData { LogoURL: template.URL("https://koder.sbs/logo.png"), SupportEmail: "support@koder.sbs", Tagline: "Koder turns every problem into an instant feedback loop.", + PreviewTitle: "Reset Your Password — Koder", + PreheaderText: "Hi Ada, we received a password reset request for your Koder account.", } } @@ -27,11 +29,16 @@ func TestRenderPasswordReset_ContainsBrandAndStructure(t *testing.T) { "", ``, ``, + "Reset Your Password — Koder", + "display:none;font-size:1px;color:#FFFFFF;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;", + "Hi Ada, we received a password reset request", "Reset your password", "Hi Ada", "https://koder.sbs/reset-password?token=abc123&x=1", "https://koder.sbs/logo.png", "background-color:#D4AF37", + "border-bottom:2px solid #B8941F", + "box-shadow:0 4px 14px rgba(212,175,55,0.35)", "Reset Password", "This secure link expires in 1 hour.", "Didn't request this?", @@ -41,6 +48,9 @@ func TestRenderPasswordReset_ContainsBrandAndStructure(t *testing.T) { "Koder turns every problem into an instant feedback loop.", "© ", "Koder", + "border-left:4px solid #7F56D9", + "border-top:2px solid #7F56D9", + "color:#53389E", } for _, want := range required { if !strings.Contains(out, want) { @@ -91,7 +101,7 @@ func TestRenderPasswordReset_AppliesDefaults(t *testing.T) { if err != nil { t.Fatalf("render failed: %v", err) } - for _, want := range []string{"Koder", "1 hour"} { + for _, want := range []string{"Koder", "1 hour", "Koder"} { if !strings.Contains(out, want) { t.Errorf("default not applied, missing %q", want) } @@ -160,7 +170,9 @@ func TestRenderBestPractices_ContainsBrandAndStructure(t *testing.T) { {UserName: "Alice", ProblemTitle: "Sum Two Numbers", Language: "go", Likes: 24}, {UserName: "Bob", ProblemTitle: "Fibonacci", Language: "python", Likes: 19}, }, - Year: 2026, + Year: 2026, + PreviewTitle: "Discover Best Practices — Koder", + PreheaderText: "See top-rated community solutions, get AI-powered code analysis, and learn from the best developers.", } out, err := RenderBestPracticesString(data) @@ -172,7 +184,10 @@ func TestRenderBestPractices_ContainsBrandAndStructure(t *testing.T) { "", ``, ``, - "Introducing Best Practices", + "Discover Best Practices — Koder", + "display:none;font-size:1px;color:#FFFFFF;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;", + "See top-rated community solutions", + "Discover Best Practices", "See how top developers solve real problems", "42", // solution count "18", // developer count @@ -194,6 +209,13 @@ func TestRenderBestPractices_ContainsBrandAndStructure(t *testing.T) { "mailto:support@koder.sbs", "Koder turns every problem into an instant feedback loop.", "© ", + // Purple accent checks + "border-left:4px solid #7F56D9", + "border-top:3px solid #7F56D9", + "border-top:2px solid #7F56D9", + "color:#53389E", + "box-shadow:0 4px 14px rgba(212,175,55,0.35)", + "border-bottom:2px solid #B8941F", } for _, want := range required { if !strings.Contains(out, want) { @@ -294,7 +316,7 @@ func TestRenderBestPractices_AppliesDefaults(t *testing.T) { if err != nil { t.Fatalf("render failed: %v", err) } - for _, want := range []string{"Koder", "Explore Best Practices"} { + for _, want := range []string{"Koder", "Explore Best Practices", "Koder"} { if !strings.Contains(out, want) { t.Errorf("default not applied, missing %q", want) } @@ -313,6 +335,8 @@ func TestRenderProblemReminderString_ContainsLightThemeStyling(t *testing.T) { SupportEmail: "support@koder.sbs", Tagline: "Koder turns every problem into an instant feedback loop.", Year: 2026, + PreviewTitle: "Binary Search — Koder", + PreheaderText: "A new challenge is waiting: Binary Search.", } out, err := RenderProblemReminderString(data) @@ -322,6 +346,9 @@ func TestRenderProblemReminderString_ContainsLightThemeStyling(t *testing.T) { for _, want := range []string{ ``, + "Binary Search — Koder", + "display:none;font-size:1px;color:#FFFFFF;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;", + "A new challenge is waiting: Binary Search", "background-color:#F7F8FA", "background-color:#FFFFFF", "background-color:#D4AF37", @@ -331,6 +358,8 @@ func TestRenderProblemReminderString_ContainsLightThemeStyling(t *testing.T) { "support@koder.sbs", "mailto:support@koder.sbs", "Koder turns every problem into an instant feedback loop.", + "border-left:4px solid #7F56D9", + "color:#53389E", } { if !strings.Contains(out, want) { t.Errorf("rendered reminder missing %q", want) diff --git a/internal/email/problem_reminder_test.go b/internal/email/problem_reminder_test.go index 3f0bf3c0..ff255548 100644 --- a/internal/email/problem_reminder_test.go +++ b/internal/email/problem_reminder_test.go @@ -17,6 +17,8 @@ func TestRenderProblemReminder_ContainsExpectedFields(t *testing.T) { LogoURL: "https://koder.sbs/logo.png", SupportEmail: "support@koder.sbs", Tagline: "Koder turns every problem into an instant feedback loop.", + PreviewTitle: "Sum Two Numbers — Koder", + PreheaderText: "A new challenge is waiting: Sum Two Numbers. Sharpen your skills with this short exercise.", } out, err := RenderProblemReminderString(data) @@ -26,12 +28,20 @@ func TestRenderProblemReminder_ContainsExpectedFields(t *testing.T) { required := []string{ "", + "Sum Two Numbers — Koder", + "display:none;font-size:1px;color:#FFFFFF;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;mso-hide:all;", + "A new challenge is waiting: Sum Two Numbers", "Ready for a quick challenge?", "Sum Two Numbers", "Add two integers and return the sum.", "Open Problem", "https://koder.sbs/logo.png", "support@koder.sbs", + "border-left:4px solid #7F56D9", + "border-top:2px solid #7F56D9", + "color:#53389E", + "border-bottom:2px solid #B8941F", + "box-shadow:0 4px 14px rgba(212,175,55,0.35)", } for _, want := range required { From 5ab23e749bcaac72ca66899e410b8e170ef4ef64 Mon Sep 17 00:00:00 2001 From: juchechu Date: Mon, 17 Aug 2026 23:40:51 +0100 Subject: [PATCH 2/2] feat: add 'Sent by Jerry Koko from Koder' to email footers --- internal/email/email.go | 3 +++ internal/email/email_test.go | 1 + 2 files changed, 4 insertions(+) diff --git a/internal/email/email.go b/internal/email/email.go index a69f4dc5..ad295e7f 100644 --- a/internal/email/email.go +++ b/internal/email/email.go @@ -508,6 +508,7 @@ See how top developers solve real problems. Browse community solutions, get AI-p
{{.PlatformName}}
{{.Tagline}}
+
Sent by Jerry Koko from Koder
© {{.Year}} {{.PlatformName}}. All rights reserved.
@@ -594,6 +595,7 @@ Sharpen your skills with this short exercise: {{.PlatformName}}
{{.Tagline}}
+
Sent by Jerry Koko from Koder
© {{.Year}} {{.PlatformName}}. All rights reserved.
@@ -751,6 +753,7 @@ This secure link expires in {{.Expire
{{.PlatformName}}
{{.Tagline}}
+
Sent by Jerry Koko from Koder
© {{.Year}} {{.PlatformName}}. All rights reserved.
diff --git a/internal/email/email_test.go b/internal/email/email_test.go index 61c74e94..a2720e56 100644 --- a/internal/email/email_test.go +++ b/internal/email/email_test.go @@ -46,6 +46,7 @@ func TestRenderPasswordReset_ContainsBrandAndStructure(t *testing.T) { "support@koder.sbs", "mailto:support@koder.sbs", "Koder turns every problem into an instant feedback loop.", + "Sent by Jerry Koko from Koder", "© ", "Koder", "border-left:4px solid #7F56D9",
@@ -691,8 +704,8 @@ If you made this request, click the button below to choose a new password. -
-Reset Password + +Reset Password
@@ -724,9 +737,9 @@ This secure link expires in {{.Expire
-
+
Button not working?
- +
+ -
{{.PlatformName}}
+
{{.PlatformName}}
{{.Tagline}}
- +
© {{.Year}} {{.PlatformName}}. All rights reserved.