From 9b0fc50ddc63061bf35a93d8af6d5b0376ce6605 Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Sun, 29 Mar 2026 17:17:24 -0400 Subject: [PATCH 1/3] more --- frontend/src/pages/subscribe/Subscribe.jsx | 150 ++------------------- 1 file changed, 8 insertions(+), 142 deletions(-) diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index 4031120..7269afa 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -12,141 +12,9 @@ import TierPicker from "../../components/TierPicker"; import "./Subscribe.css"; const STEPS = ["Create Account", "Choose Plan"]; - export default function Subscribe() { - const { currentUser, signup } = useAuth(); - const navigate = useNavigate(); - const [searchParams, setSearchParams] = useSearchParams(); - const hasPaymentReturn = searchParams.get("payment") === "success"; - const [step, setStep] = useState(hasPaymentReturn || currentUser ? 2 : 1); - const [loading, setLoading] = useState(hasPaymentReturn); - const [error, setError] = useState(""); - const [data, setData] = useState({ - selectedTier: "", - email: "", - password: "", - confirmPassword: "", - }); - - function set(field, value) { - setData((prev) => ({ ...prev, [field]: value })); - } - - // Handle Stripe payment returns - useEffect(() => { - const payment = searchParams.get("payment"); - const sessionId = searchParams.get("session_id"); - - if (payment === "cancelled") { - setSearchParams({}, { replace: true }); - setStep(2); - setError("Payment was cancelled. Please try again."); - return; - } - - if (payment !== "success" || !sessionId) return; - - // Phase 1: no account yet — create it. Auth state change remounts the - // component (App.jsx swaps route trees) and Phase 2 runs on the next mount. - if (!currentUser) { - const savedEmail = sessionStorage.getItem("kp_onboard_email"); - const savedPw = sessionStorage.getItem("kp_onboard_pw"); - if (!savedEmail || !savedPw) { - setSearchParams({}, { replace: true }); - setError("Session expired. Please start the signup process again."); - setStep(1); - setLoading(false); - return; - } - setLoading(true); - signup(savedEmail, savedPw).catch((err) => { - setSearchParams({}, { replace: true }); - setError("Account creation failed: " + err.message); - setStep(1); - setLoading(false); - }); - return; - } - - // Phase 2: authenticated — verify the checkout and go to the dashboard. - setSearchParams({}, { replace: true }); - setLoading(true); - sessionStorage.removeItem("kp_onboard_email"); - sessionStorage.removeItem("kp_onboard_pw"); - - verifyCheckoutSession(sessionId) - .then(() => { - navigate("/addresses", { replace: true }); - }) - .catch((err) => { - setError("Payment verification failed: " + err.message); - setStep(2); - }) - .finally(() => setLoading(false)); - }, [currentUser, searchParams, setSearchParams, signup, navigate]); - - // ── Step handlers ───────────────────────────────────────────────────────── - - function handleStep1() { - setError(""); - if (!data.email || !data.password || !data.confirmPassword) { - setError("Please fill in all fields"); - return; - } - if (data.password !== data.confirmPassword) { - setError("Passwords do not match"); - return; - } - if (data.password.length < 6) { - setError("Password must be at least 6 characters"); - return; - } - setStep(2); - } - - async function handleStep2() { - setError(""); - if (!data.selectedTier) { - setError("Please select a plan to continue"); - return; - } - try { - setLoading(true); - - if (data.selectedTier === "free") { - if (!currentUser) { - await signup(data.email, data.password); - } - await activateFreeTier(); - navigate("/addresses", { replace: true }); - return; - } - - sessionStorage.setItem("kp_onboard_email", data.email); - sessionStorage.setItem("kp_onboard_pw", data.password); - const { url } = await createOnboardingCheckout( - data.email, - data.selectedTier, - ); - window.location.href = url; - } catch (err) { - if (err.code === "auth/email-already-in-use") { - setError("Email already in use. Try logging in instead."); - } else if (err.code === "auth/invalid-email") { - setError("Invalid email address"); - } else if (err.code === "auth/weak-password") { - setError("Password is too weak"); - } else { - setError("Failed to process plan selection: " + err.message); - } - } finally { - setLoading(false); - } - } - - // ── Progress bar ────────────────────────────────────────────────────────── function ProgressBar() { return ( @@ -165,11 +33,10 @@ export default function Subscribe() {
{i > 0 && (
)}
@@ -177,11 +44,10 @@ export default function Subscribe() { {done ? "\u2713" : stepNum}
{label}
From 2ca10ea340a1e76959dac07391dac74c819c2b04 Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Sun, 29 Mar 2026 18:36:28 -0400 Subject: [PATCH 2/3] more --- backend/internal/handlers/stripe.go | 4 +- frontend/src/App.jsx | 4 + .../src/pages/subscribe/CheckoutReturn.jsx | 54 ++++++++++++++ frontend/src/pages/subscribe/Subscribe.jsx | 73 ++++++++++++++++++- 4 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 frontend/src/pages/subscribe/CheckoutReturn.jsx diff --git a/backend/internal/handlers/stripe.go b/backend/internal/handlers/stripe.go index 83cb61b..624428c 100644 --- a/backend/internal/handlers/stripe.go +++ b/backend/internal/handlers/stripe.go @@ -84,7 +84,7 @@ func (h *StripeHandler) CreateCheckoutSession(w http.ResponseWriter, r *http.Req Quantity: stripe.Int64(1), }, }, - SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), + SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), ClientReferenceID: stripe.String(userID), CustomerEmail: stripe.String(user.Email), @@ -246,7 +246,7 @@ func (h *StripeHandler) CreateOnboardingCheckout(w http.ResponseWriter, r *http. Quantity: stripe.Int64(1), }, }, - SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=success&session_id={CHECKOUT_SESSION_ID}"), + SuccessURL: stripe.String(h.cfg.FrontendURL + "/subscribe/return/{CHECKOUT_SESSION_ID}"), CancelURL: stripe.String(h.cfg.FrontendURL + "/subscribe?payment=cancelled"), CustomerEmail: stripe.String(body.Email), } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 47fb0d3..014f142 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,6 +4,7 @@ import Navbar from "./components/Navbar"; import Login from "./pages/login/Login"; import Signup from "./pages/Signup"; import Subscribe from "./pages/subscribe/Subscribe"; +import CheckoutReturn from "./pages/subscribe/CheckoutReturn"; import Addresses from "./pages/addresses/Addresses"; import Alerts from "./pages/alerts/Alerts"; import AlertHistory from "./pages/alertHistory/AlertHistory"; @@ -18,6 +19,7 @@ export default function App() { } /> } /> } /> + } /> } /> ); @@ -27,6 +29,7 @@ export default function App() { return ( } /> + } /> } /> } /> @@ -43,6 +46,7 @@ export default function App() { } /> } /> } /> + } /> } />
diff --git a/frontend/src/pages/subscribe/CheckoutReturn.jsx b/frontend/src/pages/subscribe/CheckoutReturn.jsx new file mode 100644 index 0000000..52b667c --- /dev/null +++ b/frontend/src/pages/subscribe/CheckoutReturn.jsx @@ -0,0 +1,54 @@ +import { useState, useRef } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { verifyCheckoutSession } from "../../api/stripe"; +import { useAuth } from "../../contexts/AuthContext"; +import "./Subscribe.css"; + +export default function CheckoutReturn() { + const { sessionId } = useParams(); + const navigate = useNavigate(); + const { refreshAccount } = useAuth(); + const [error, setError] = useState(null); + const started = useRef(false); + + if (sessionId && !started.current) { + started.current = true; + verifyCheckoutSession(sessionId) + .then(() => refreshAccount()) + .then(() => navigate("/addresses", { replace: true })) + .catch((err) => setError(err.message || "Payment verification failed")); + } + + if (!sessionId) { + return ( +
+
+

Koin Ping

+
No session ID found.
+

Back to Subscribe

+
+
+ ); + } + + if (error) { + return ( +
+
+

Koin Ping

+
{error}
+

Try again

+
+
+ ); + } + + return ( +
+
+

Koin Ping

+

Verifying your payment...

+
+
+ ); +} diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index 7269afa..c7d7dbd 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -1,9 +1,8 @@ -import { useState, useEffect } from "react"; +import { useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import { useAuth } from "../../contexts/AuthContext"; import { - createOnboardingCheckout, - verifyCheckoutSession, + createCheckoutSession, activateFreeTier, } from "../../api/stripe"; import Input from "../../components/Input"; @@ -13,6 +12,74 @@ import "./Subscribe.css"; const STEPS = ["Create Account", "Choose Plan"]; export default function Subscribe() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const { currentUser, signup, refreshAccount } = useAuth(); + + const [step, setStep] = useState(currentUser ? 2 : 1); + const [data, setData] = useState({ + email: currentUser?.email || "", + password: "", + confirmPassword: "", + selectedTier: null, + }); + const [error, setError] = useState( + searchParams.get("payment") === "cancelled" + ? "Payment was cancelled. Please try again." + : "" + ); + const [loading, setLoading] = useState(false); + + function set(field, value) { + setData((prev) => ({ ...prev, [field]: value })); + } + + async function handleStep1() { + setError(""); + if (!data.email || !data.password) { + setError("Email and password are required"); + return; + } + if (data.password.length < 6) { + setError("Password must be at least 6 characters"); + return; + } + if (data.password !== data.confirmPassword) { + setError("Passwords do not match"); + return; + } + setLoading(true); + try { + await signup(data.email, data.password); + setStep(2); + } catch (err) { + setError(err.message || "Failed to create account"); + } finally { + setLoading(false); + } + } + + async function handleStep2() { + setError(""); + if (!data.selectedTier) { + setError("Please select a plan"); + return; + } + setLoading(true); + try { + if (data.selectedTier === "free") { + await activateFreeTier(); + await refreshAccount(); + navigate("/addresses", { replace: true }); + } else { + const { url } = await createCheckoutSession(data.selectedTier); + window.location.href = url; + } + } catch (err) { + setError(err.message || "Something went wrong"); + setLoading(false); + } + } From 9b1a4cc0e2687be186f4560c111d6e2517dbd24f Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Mon, 11 May 2026 12:46:20 -0400 Subject: [PATCH 3/3] Minor updates to Subscribe.js, updated LLM_DEV_PROMPTS to latest version --- LLM_DEV_PROMPTS/.dockerignore | 3 + LLM_DEV_PROMPTS/.gitignore | 26 + .../ANGULAR_BEST_PRACTICES.md | 1561 +++++++++++++++++ .../CODE_STYLEGUIDE_ANGULAR.md | 47 + LLM_DEV_PROMPTS/A_EXISTING_REPO_CHECKLIST.md | 79 + LLM_DEV_PROMPTS/B_NEW_REPO_CHECKLIST.md | 88 + LLM_DEV_PROMPTS/C_GENERAL_CODE_STYLEGUIDE.md | 76 + LLM_DEV_PROMPTS/D_REPO_POLICIES.md | 183 ++ LLM_DEV_PROMPTS/Dockerfile | 11 + .../GOLANG PROJECTS/CODE_STYLEGUIDE_GO.md | 575 ++++++ .../GO_HTTP_SERVER_CONVENTIONS.md | 1242 +++++++++++++ LLM_DEV_PROMPTS/LICENSE | 21 + LLM_DEV_PROMPTS/Makefile | 31 + .../NODEJS PROJECTS/CODE_STYLEGUIDE_JS.md | 81 + .../NODE_BACKEND_ARCH_BASICS.md | 30 + .../PYTHON PROJECTS/CODE_STYLEGUIDE_PYTHON.md | 54 + LLM_DEV_PROMPTS/README.md | 139 ++ .../CODE_STYLEGUIDE_TYPESCRIPT.md | 214 +++ LLM_DEV_PROMPTS/package.json | 5 + frontend/src/pages/subscribe/Subscribe.jsx | 14 + 20 files changed, 4480 insertions(+) create mode 100644 LLM_DEV_PROMPTS/.dockerignore create mode 100644 LLM_DEV_PROMPTS/.gitignore create mode 100644 LLM_DEV_PROMPTS/ANGULAR PROJECTS/ANGULAR_BEST_PRACTICES.md create mode 100644 LLM_DEV_PROMPTS/ANGULAR PROJECTS/CODE_STYLEGUIDE_ANGULAR.md create mode 100644 LLM_DEV_PROMPTS/A_EXISTING_REPO_CHECKLIST.md create mode 100644 LLM_DEV_PROMPTS/B_NEW_REPO_CHECKLIST.md create mode 100644 LLM_DEV_PROMPTS/C_GENERAL_CODE_STYLEGUIDE.md create mode 100644 LLM_DEV_PROMPTS/D_REPO_POLICIES.md create mode 100644 LLM_DEV_PROMPTS/Dockerfile create mode 100644 LLM_DEV_PROMPTS/GOLANG PROJECTS/CODE_STYLEGUIDE_GO.md create mode 100644 LLM_DEV_PROMPTS/GOLANG PROJECTS/GO_HTTP_SERVER_CONVENTIONS.md create mode 100644 LLM_DEV_PROMPTS/LICENSE create mode 100644 LLM_DEV_PROMPTS/Makefile create mode 100644 LLM_DEV_PROMPTS/NODEJS PROJECTS/CODE_STYLEGUIDE_JS.md create mode 100644 LLM_DEV_PROMPTS/NODEJS PROJECTS/NODE_BACKEND_ARCH_BASICS.md create mode 100644 LLM_DEV_PROMPTS/PYTHON PROJECTS/CODE_STYLEGUIDE_PYTHON.md create mode 100644 LLM_DEV_PROMPTS/README.md create mode 100644 LLM_DEV_PROMPTS/TYPESCRIPT PROJECTS/CODE_STYLEGUIDE_TYPESCRIPT.md create mode 100644 LLM_DEV_PROMPTS/package.json diff --git a/LLM_DEV_PROMPTS/.dockerignore b/LLM_DEV_PROMPTS/.dockerignore new file mode 100644 index 0000000..5414d56 --- /dev/null +++ b/LLM_DEV_PROMPTS/.dockerignore @@ -0,0 +1,3 @@ +.git +node_modules +.DS_Store diff --git a/LLM_DEV_PROMPTS/.gitignore b/LLM_DEV_PROMPTS/.gitignore new file mode 100644 index 0000000..afdb9cf --- /dev/null +++ b/LLM_DEV_PROMPTS/.gitignore @@ -0,0 +1,26 @@ +# OS +.DS_Store +Thumbs.db + +# Editors +*.swp +*.swo +*~ +*.bak +.idea/ +.vscode/ +*.sublime-* + +# Node +node_modules/ + +# Environment / secrets +.env +.env.* +*.pem +*.key + +# Prompts +prompts/ +*prompts +prompts* \ No newline at end of file diff --git a/LLM_DEV_PROMPTS/ANGULAR PROJECTS/ANGULAR_BEST_PRACTICES.md b/LLM_DEV_PROMPTS/ANGULAR PROJECTS/ANGULAR_BEST_PRACTICES.md new file mode 100644 index 0000000..8ecf0d6 --- /dev/null +++ b/LLM_DEV_PROMPTS/ANGULAR PROJECTS/ANGULAR_BEST_PRACTICES.md @@ -0,0 +1,1561 @@ +## Introduction + +1. This guide covers a range of style conventions for Angular application code. These recommendations +are not required for Angular to work, but instead establish a set of coding practices that promote +consistency across the Angular ecosystem. A consistent set of practices makes it easier to share +code and move between projects. + +2. This guide does _not_ cover TypeScript or general coding practices unrelated to Angular. For +TypeScript, check +out [Google's TypeScript style guide](https://google.github.io/styleguide/tsguide.html). + +### When in doubt, prefer consistency + +1. Whenever you encounter a situation in which these rules contradict the style of a particular file, +prioritize maintaining consistency within a file. Mixing different style conventions in a single +file creates more confusion than diverging from the recommendations in this guide. + +## Naming + +### Separate words in file names with hyphens + +1. Separate words within a file name with hyphens (`-`). For example, a component named `UserProfile` +has a file name `user-profile.ts`. + +### Use the same name for a file's tests with `.spec` at the end + +1. For unit tests, end file names with `.spec.ts`. For example, the unit test file for +the `UserProfile` component has the file name `user-profile.spec.ts`. + +### Match file names to the TypeScript identifier within + +1. File names should generally describe the contents of the code in the file. When the file contains a +TypeScript class, the file name should reflect that class name. For example, a file containing a +component named `UserProfile` has the name `user-profile.ts`. + +2. If the file contains more than one primary namable identifier, choose a name that describes the +common theme to the code within. If the code in a file does not fit within a common theme or feature +area, consider breaking the code up into different files. Do not use overly generic file names +like `helpers.ts`, `utils.ts`, or `common.ts`. + +### Use the same file name for a component's TypeScript, template, and styles + +1. Components typically consist of one TypeScript file, one template file, and one style file. These +files should share the same name with different file extensions. For example, a `UserProfile` +component can have the files `user-profile.ts`, `user-profile.html`, and `user-profile.css`. + +2. If a component has more than one style file, append the name with additional words that describe the +styles specific to that file. For example, `UserProfile` might have style +files `user-profile-settings.css` and `user-profile-subscription.css`. + +## Project structure + +### All the application's code goes in a directory named `src` + +1. All of your Angular UI code (TypeScript, HTML, and styles) should live inside a directory +named `src`. Code that's not related to UI, such as configuration files or scripts, should live +outside the `src` directory. + +2. This keeps the root application directory consistent between different Angular projects and creates +a clear separation between UI code and other code in your project. + +### Bootstrap your application in a file named `main.ts` directly inside `src` + +1. The code to start up, or **bootstrap**, an Angular application should always live in a file +named `main.ts`. This represents the primary entry point to the application. + +### Group closely related files together in the same directory + +1. Angular components consist of a TypeScript file and, optionally, a template and one or more style +files. You should group these together in the same directory. + +2. Unit tests should live in the same directory as the code-under-test. Do not collect unrelated +tests into a single `tests` directory. + +### Organize your project by feature areas + +1. Organize your project into subdirectories based on the features of your application or common themes +to the code in those directories. For example, the project structure for a movie theater site, +MovieReel, might look like this: + +``` +src/ +├─ movie-reel/ +│ ├─ show-times/ +│ │ ├─ film-calendar/ +│ │ ├─ film-details/ +│ ├─ reserve-tickets/ +│ │ ├─ payment-info/ +│ │ ├─ purchase-confirmation/ +``` + +2. Do not create subdirectories based on the type of code that lives in those directories. For +example, do not create directories like `components`, `directives`, and `services`. + +3. Do not put so many files into one directory that it becomes hard to read or navigate. As the +number of files in a directory grows, consider splitting further into additional sub-directories. + +### One concept per file + +1. Prefer focusing source files on a single _concept_. For Angular classes specifically, this usually +means one component, directive, or service per file. However, it's okay if a file contains more than +one component or directive if your classes are relatively small and they tie together as part of a +single concept. + +2. When in doubt, go with the approach that leads to smaller files. + +## Dependency injection + +### Prefer the `inject` function over constructor parameter injection + +1. Prefer using the [`inject`](/api/core/inject) function over injecting constructor parameters. The [`inject`](/api/core/inject) function works the same way as constructor parameter injection, but offers several style advantages: + +- [`inject`](/api/core/inject) is generally more readable, especially when a class injects many dependencies. +- It's more syntactically straightforward to add comments to injected dependencies +- [`inject`](/api/core/inject) offers better type inference. +- When targeting ES2022+ with [`useDefineForClassFields`](https://www.typescriptlang.org/tsconfig/#useDefineForClassFields), you can avoid separating field declaration and initialization when fields read on injected dependencies. + +2. [You can refactor existing code to `inject` with an automatic tool](reference/migrations/inject-function). + +## Components and directives + +### Choosing component selectors + +1. See +the [Components guide for details on choosing component selectors](guide/components/selectors#choosing-a-selector). + +### Naming component and directive members + +1. See the Components guide for details +on [naming input properties](guide/components/inputs#choosing-input-names) +and [naming output properties](guide/components/outputs#choosing-event-names). + +### Choosing directive selectors + +1. Directives should use the +same [application-specific prefix](guide/components/selectors#selector-prefixes) +as your components. + +2. When using an attribute selector for a directive, use a camelCase attribute name. For example, if +your application is named "MovieReel" and you build a directive that adds a tooltip to an element, +you might use the selector `[mrTooltip]`. + +### Group Angular-specific properties before methods + +1. Components and directives should group Angular-specific properties together, typically near the top +of the class declaration. This includes injected dependencies, inputs, outputs, and queries. Define +these and other properties before the class's methods. + +2. This practice makes it easier to find the class's template APIs and dependencies. + +### Keep components and directives focused on presentation + +1. Code inside your components and directives should generally relate to the UI shown on the page. For +code that makes sense on its own, decoupled from the UI, prefer refactoring to other files. For +example, you can factor form validation rules or data transformations into separate functions or +classes. + +### Do not use overly complex logic in templates + +1. Angular templates are designed to +accommodate [JavaScript-like expressions](guide/templates/expression-syntax). +You should take advantage of these expressions to capture relatively straightforward logic directly +in template expressions. + +2. When the code in a template gets too complex, refactor logic into the TypeScript code (typically with a [computed](guide/signals#computed-signals)). + +3. There's no one hard-and-fast rule that determines what constitutes "complex". Use your best +judgement. + +### Use `protected` on class members that are only used by a component's template + +1. A component class's public members intrinsically define a public API that's accessible via +dependency injection and [queries](guide/components/queries). Prefer `protected` +access for any members that are meant to be read from the component's template. + +```ts +@Component({ + ..., + template: `

{{ fullName() }}

`, +}) +export class UserProfile { + firstName = input(); + lastName = input(); + +// `fullName` is not part of the component's public API, but is used in the template. + protected fullName = computed(() => `${this.firstName()} ${this.lastName()}`); +} +``` + +### Use `readonly` for properties that shouldn't change + +1. Mark component and directive properties initialized by Angular as `readonly`. This includes +properties initialized by `input`, `model`, `output`, and queries. The readonly access modifier +ensures that the value set by Angular is not overwritten. + +```ts +@Component({ + /*...*/ +}) +export class UserProfile { + readonly userId = input(); + readonly userSaved = output(); + readonly userName = model(); +} +``` + +2. For components and directives that use the decorator-based `@Input`, `@Output`, and query APIs, this +advice applies to output properties and queries, but not input properties. + +```ts +@Component({ + /*...*/ +}) +export class UserProfile { + @Output() readonly userSaved = new EventEmitter(); + @ViewChildren(PaymentMethod) readonly paymentMethods?: QueryList; +} +``` + +### Prefer `class` and `style` over `ngClass` and `ngStyle` + +1. Prefer `class` and `style` bindings over using the [`NgClass`](/api/common/NgClass) and [`NgStyle`](/api/common/NgStyle) directives. + +```html +{prefer} +
+
+ +
+
+
+
+
+``` + +```html +{avoid} +
+
+
+``` + +2. Both `class` and `style` bindings use a more straightforward syntax that aligns closely with +standard HTML attributes. This makes your templates easier to read and understand, especially for +developers familiar with basic HTML. + +3. Additionally, the `NgClass` and `NgStyle` directives incur an additional performance cost compared +to the built-in `class` and `style` binding syntax. + +4. For more details, refer to the [bindings guide](/guide/templates/binding#css-class-and-style-property-bindings) + +### Name event handlers for what they _do_, not for the triggering event + +1. Prefer naming event handlers for the action they perform rather than for the triggering event: + +```html +{prefer} + +``` + +```html +{avoid} + +``` + +2. Using meaningful names like this makes it easier to tell what an event does from reading the +template. + +3. For keyboard events, you can use Angular's key event modifiers with specific handler names: + +```html +