← back to Open Seo

src/client/features/keywords/keywordResearchTypes.ts

81 lines

import type { KeywordIntent } from "@/types/keywords";

export const MAX_KEYWORDS_PER_SUBMIT = 5;

export type ResultLimit = 150 | 300 | 500;
export const RESULT_LIMITS: ResultLimit[] = [150, 300, 500];

export type KeywordSource = "related" | "suggestions" | "ideas";
export type KeywordMode = "auto" | KeywordSource;
/** Actual result source; google_ads serves countries Labs doesn't cover. */
export type ResearchSource = KeywordSource | "google_ads";

export type KeywordFilterValues = {
  include: string;
  exclude: string;
  minVol: string;
  maxVol: string;
  minCpc: string;
  maxCpc: string;
  minKd: string;
  maxKd: string;
  /**
   * Selected search intents, stored as a comma-separated string (e.g.
   * "transactional,commercial") so it fits the all-strings filter shape used
   * for form state, persistence, and active-filter counting. Empty = no intent
   * filter. Use {@link parseIntentFilter} / {@link toggleIntentFilter} to read
   * and edit it rather than parsing the string ad hoc.
   */
  intents: string;
};

export const EMPTY_FILTERS: KeywordFilterValues = {
  include: "",
  exclude: "",
  minVol: "",
  maxVol: "",
  minCpc: "",
  maxCpc: "",
  minKd: "",
  maxKd: "",
  intents: "",
};

/** Canonical intent order for stable serialization and UI display. */
export const KEYWORD_INTENT_ORDER: KeywordIntent[] = [
  "informational",
  "commercial",
  "transactional",
  "navigational",
  "unknown",
];

const KEYWORD_INTENT_SET = new Set<string>(KEYWORD_INTENT_ORDER);

/** Parses the stored intents string into a list of valid, de-duplicated intents. */
export function parseIntentFilter(value: string): KeywordIntent[] {
  if (!value) return [];
  const selected = new Set(
    value
      .split(",")
      .map((part) => part.trim())
      .filter((part): part is KeywordIntent => KEYWORD_INTENT_SET.has(part)),
  );
  // Emit in canonical order so persistence and comparisons are deterministic.
  return KEYWORD_INTENT_ORDER.filter((intent) => selected.has(intent));
}

/** Toggles one intent in the stored string, preserving canonical order. */
export function toggleIntentFilter(
  value: string,
  intent: KeywordIntent,
): string {
  const selected = new Set(parseIntentFilter(value));
  if (selected.has(intent)) {
    selected.delete(intent);
  } else {
    selected.add(intent);
  }
  return KEYWORD_INTENT_ORDER.filter((item) => selected.has(item)).join(",");
}