@fewchurs/react 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core.d.mts +5 -4
- package/dist/core.d.ts +5 -4
- package/dist/core.js +44 -24
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +43 -24
- package/dist/core.mjs.map +1 -1
- package/dist/index.d.mts +14 -4
- package/dist/index.d.ts +14 -4
- package/dist/index.js +137 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +146 -64
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +21 -0
- package/package.json +1 -1
package/dist/core.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/client/version.ts","../src/client/constants.ts","../src/client/errors.ts","../src/client/normalize.ts","../src/client/validate.ts","../src/client/client.ts","../src/identity/deviceId.ts","../src/store/keys.ts"],"sourcesContent":["// Generated by tools/stamp-version.mjs from @fewchurs/react's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.4.0\";\n","import type { FeatureStatus } from \"../types\";\nimport { SDK_VERSION } from \"./version\";\n\nexport { SDK_VERSION };\nexport const SDK_HEADER = `react/${SDK_VERSION}`;\nexport const DEFAULT_BASE_URL = \"https://www.fewchurs.com/api/v1\";\nexport const DEFAULT_PAGE_SIZE = 20;\n\n/** Mirrors the server's zod schemas, so the UI can reject input before a round trip. */\nexport const LIMITS = {\n titleMin: 3,\n titleMax: 100,\n descriptionMax: 1000,\n commentMin: 1,\n commentMax: 1000,\n authorNameMax: 60,\n} as const;\n\n/** Deliberately loose: the server owns the real check, this only catches obvious typos. */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** What the Roadmap tab asks for, and what it keeps if the server ignores the filter. */\nexport const ROADMAP_STATUSES: FeatureStatus[] = [\"planned\", \"in_progress\", \"done\"];\n","export type FewchursError =\n | { code: \"invalid_key\"; status: 401 | 403; message: string }\n | { code: \"validation_failed\"; status: 400; message: string }\n | { code: \"rate_limited\"; status: 429; message: string }\n | { code: \"not_found\"; status: 404; message: string }\n | { code: \"limit_reached\"; status: 402; message: string }\n | { code: \"server\"; status: number; message: string }\n | { code: \"network\"; message: string };\n\nexport type Result<T> = { ok: true; data: T } | { ok: false; error: FewchursError };\n\ninterface ServerErrorBody {\n error?: { code?: string; message?: string };\n}\n\nconst USER_MESSAGES: Record<FewchursError[\"code\"], string> = {\n invalid_key: \"Feedback is unavailable right now.\",\n validation_failed: \"Please check what you entered and try again.\",\n rate_limited: \"You have reached today's limit. Try again tomorrow.\",\n not_found: \"That request no longer exists.\",\n limit_reached: \"Feedback is unavailable right now.\",\n server: \"Something went wrong. Please try again.\",\n network: \"Could not reach the server. Check your connection.\",\n};\n\n/** Safe to show a visitor: never leaks a server message that may name internals. */\nexport function userMessage(error: FewchursError): string {\n if (error.code === \"validation_failed\" && error.message) return error.message;\n return USER_MESSAGES[error.code];\n}\n\nexport function networkError(message = \"Network request failed.\"): FewchursError {\n return { code: \"network\", message };\n}\n\nexport function validationError(message: string): FewchursError {\n return { code: \"validation_failed\", status: 400, message };\n}\n\nexport function errorFromResponse(status: number, body: unknown): FewchursError {\n const code = (body as ServerErrorBody | null)?.error?.code;\n const message = (body as ServerErrorBody | null)?.error?.message ?? `Request failed (${status}).`;\n\n switch (code) {\n case \"invalid_key\":\n return { code: \"invalid_key\", status: 401, message };\n case \"wrong_app\":\n return { code: \"invalid_key\", status: 403, message };\n // missing_device_id is a client mistake the caller fixes the same way a bad field is fixed,\n // so it collapses into validation_failed rather than widening the public union.\n case \"missing_device_id\":\n case \"validation_failed\":\n return { code: \"validation_failed\", status: 400, message };\n case \"rate_limited\":\n return { code: \"rate_limited\", status: 429, message };\n case \"not_found\":\n return { code: \"not_found\", status: 404, message };\n case \"limit_reached\":\n return { code: \"limit_reached\", status: 402, message };\n default:\n return status === 401\n ? { code: \"invalid_key\", status: 401, message }\n : { code: \"server\", status, message };\n }\n}\n","import type {\n Comment,\n CommentPage,\n Feature,\n FeaturePage,\n FeatureStatus,\n FewchursConfig,\n} from \"../types\";\n\nconst KNOWN_STATUSES = new Set<string>([\"open\", \"planned\", \"in_progress\", \"done\", \"declined\"]);\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null ? (value as Record<string, unknown>) : {};\n}\n\nfunction str(value: unknown, fallback = \"\"): string {\n return typeof value === \"string\" ? value : fallback;\n}\n\nfunction num(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction bool(value: unknown): boolean {\n return value === true;\n}\n\nexport function normalizeStatus(value: unknown): FeatureStatus {\n return typeof value === \"string\" && KNOWN_STATUSES.has(value)\n ? (value as FeatureStatus)\n : \"unknown\";\n}\n\n// `isMine` is the server's answer once it ships; until then the raw author device id is the\n// only signal, so it is compared here and never surfaced on the public type.\nfunction isMine(raw: Record<string, unknown>, ownerKey: string, deviceId: string | null): boolean {\n if (typeof raw.isMine === \"boolean\") return raw.isMine;\n const owner = raw[ownerKey];\n return typeof owner === \"string\" && owner.length > 0 && owner === deviceId;\n}\n\nexport function normalizeFeature(value: unknown, deviceId: string | null): Feature {\n const raw = asRecord(value);\n return {\n id: str(raw.id),\n title: str(raw.title),\n description: str(raw.description),\n status: normalizeStatus(raw.status),\n upvoteCount: num(raw.upvoteCount),\n commentCount: num(raw.commentCount),\n followerCount: num(raw.followerCount),\n hasVoted: bool(raw.hasVoted),\n isFollowing: bool(raw.isFollowing),\n isMine: isMine(raw, \"authorDeviceId\", deviceId),\n createdAt: str(raw.createdAt),\n updatedAt: str(raw.updatedAt, str(raw.createdAt)),\n };\n}\n\nexport function normalizeComment(value: unknown, deviceId: string | null): Comment {\n const raw = asRecord(value);\n return {\n id: str(raw.id),\n text: str(raw.text),\n authorName: str(raw.authorName, \"App User\"),\n isDeveloper: bool(raw.isDeveloper),\n isMine: isMine(raw, \"deviceId\", deviceId),\n createdAt: str(raw.createdAt),\n };\n}\n\nexport function normalizeFeaturePage(value: unknown, deviceId: string | null): FeaturePage {\n const raw = asRecord(value);\n const features = Array.isArray(raw.features) ? raw.features : [];\n return {\n features: features.map((feature) => normalizeFeature(feature, deviceId)),\n nextCursor: typeof raw.nextCursor === \"string\" ? raw.nextCursor : null,\n };\n}\n\nexport function normalizeCommentPage(value: unknown, deviceId: string | null): CommentPage {\n const raw = asRecord(value);\n const comments = Array.isArray(raw.comments) ? raw.comments : [];\n return {\n comments: comments.map((comment) => normalizeComment(comment, deviceId)),\n nextCursor: typeof raw.nextCursor === \"string\" ? raw.nextCursor : null,\n };\n}\n\nexport function normalizeConfig(value: unknown): FewchursConfig {\n const raw = asRecord(value);\n return {\n showBranding: raw.showBranding !== false,\n hideVoteCounts: bool(raw.hideVoteCounts),\n appName: typeof raw.appName === \"string\" && raw.appName ? raw.appName : null,\n };\n}\n\nexport function normalizeVote(value: unknown): { upvoteCount: number; hasVoted: boolean } {\n const raw = asRecord(value);\n return { upvoteCount: num(raw.upvoteCount), hasVoted: bool(raw.hasVoted) };\n}\n","import { EMAIL_PATTERN, LIMITS } from \"./constants\";\nimport { validationError, type FewchursError } from \"./errors\";\nimport type { CreateCommentInput, SubmitFeatureInput } from \"../types\";\n\nexport function validateFeatureInput(input: SubmitFeatureInput): FewchursError | null {\n const title = input.title.trim();\n if (title.length < LIMITS.titleMin) {\n return validationError(`Title must be at least ${LIMITS.titleMin} characters.`);\n }\n if (title.length > LIMITS.titleMax) {\n return validationError(`Title must be at most ${LIMITS.titleMax} characters.`);\n }\n if ((input.description ?? \"\").trim().length > LIMITS.descriptionMax) {\n return validationError(`Description must be at most ${LIMITS.descriptionMax} characters.`);\n }\n if (input.email !== undefined && input.email !== \"\" && !isEmail(input.email)) {\n return validationError(\"Enter a valid email address.\");\n }\n return null;\n}\n\nexport function validateCommentInput(input: CreateCommentInput): FewchursError | null {\n const text = input.text.trim();\n if (text.length < LIMITS.commentMin) return validationError(\"Write a comment first.\");\n if (text.length > LIMITS.commentMax) {\n return validationError(`Comment must be at most ${LIMITS.commentMax} characters.`);\n }\n if ((input.authorName ?? \"\").trim().length > LIMITS.authorNameMax) {\n return validationError(`Name must be at most ${LIMITS.authorNameMax} characters.`);\n }\n return null;\n}\n\nexport function validateEmail(email: string): FewchursError | null {\n return isEmail(email) ? null : validationError(\"Enter a valid email address.\");\n}\n\nfunction isEmail(email: string): boolean {\n return EMAIL_PATTERN.test(email.trim());\n}\n","import { DEFAULT_BASE_URL, DEFAULT_PAGE_SIZE, SDK_HEADER } from \"./constants\";\nimport { errorFromResponse, networkError, type FewchursError, type Result } from \"./errors\";\nimport {\n normalizeComment,\n normalizeCommentPage,\n normalizeConfig,\n normalizeFeature,\n normalizeFeaturePage,\n normalizeVote,\n} from \"./normalize\";\nimport { validateCommentInput, validateEmail, validateFeatureInput } from \"./validate\";\nimport type {\n Comment,\n CommentPage,\n CreateCommentInput,\n Feature,\n FeaturePage,\n FewchursConfig,\n ListCommentsParams,\n ListFeaturesParams,\n SubmitFeatureInput,\n} from \"../types\";\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface FewchursClientOptions {\n /** Server-side, or in a SPA where the key is publishable. Omit when calling your own proxy. */\n apiKey?: string;\n /** `https://www.fewchurs.com/api/v1` by default, or a proxy path such as `/api/fewchurs`. */\n baseUrl?: string;\n deviceId?: string | null | (() => string | null);\n /** Sent as `Accept-Language`, so server-side copy and emails match the visitor. */\n language?: string;\n /**\n * The domain the API key is checked against, for calls made from a server. A browser reports\n * its own domain through `Origin`, which always wins, so leave this out in a SPA.\n */\n appId?: string;\n fetch?: FetchLike;\n retries?: number;\n retryDelayMs?: number;\n}\n\nexport interface FewchursClient {\n readonly baseUrl: string;\n getDeviceId(): string | null;\n getConfig(signal?: AbortSignal): Promise<Result<FewchursConfig>>;\n listFeatures(params?: ListFeaturesParams, signal?: AbortSignal): Promise<Result<FeaturePage>>;\n getFeature(featureId: string, signal?: AbortSignal): Promise<Result<Feature>>;\n submitFeature(input: SubmitFeatureInput): Promise<Result<Feature>>;\n vote(featureId: string): Promise<Result<{ upvoteCount: number; hasVoted: boolean }>>;\n unvote(featureId: string): Promise<Result<{ upvoteCount: number; hasVoted: boolean }>>;\n follow(featureId: string, email: string): Promise<Result<{ following: boolean }>>;\n unfollow(featureId: string): Promise<Result<{ following: boolean }>>;\n listComments(\n featureId: string,\n params?: ListCommentsParams,\n signal?: AbortSignal,\n ): Promise<Result<CommentPage>>;\n createComment(input: CreateCommentInput): Promise<Result<Comment>>;\n}\n\ninterface RequestOptions {\n method?: \"GET\" | \"POST\" | \"DELETE\";\n body?: unknown;\n query?: Record<string, string | number | undefined | null>;\n signal?: AbortSignal;\n}\n\nexport function createFewchursClient(options: FewchursClientOptions = {}): FewchursClient {\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n const doFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);\n const retries = options.retries ?? 1;\n const retryDelayMs = options.retryDelayMs ?? 250;\n\n function getDeviceId(): string | null {\n return typeof options.deviceId === \"function\" ? options.deviceId() : (options.deviceId ?? null);\n }\n\n function headers(hasBody: boolean): Record<string, string> {\n const result: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Fewchurs-Sdk\": SDK_HEADER,\n };\n if (hasBody) result[\"Content-Type\"] = \"application/json\";\n if (options.apiKey) result.Authorization = `Bearer ${options.apiKey}`;\n const deviceId = getDeviceId();\n if (deviceId) result[\"X-Device-Id\"] = deviceId;\n if (options.language) result[\"Accept-Language\"] = options.language;\n if (options.appId) result[\"X-Fewchurs-App-Id\"] = options.appId;\n return result;\n }\n\n function url(path: string, query?: RequestOptions[\"query\"]): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value !== undefined && value !== null && value !== \"\") search.set(key, String(value));\n }\n const qs = search.toString();\n return `${baseUrl}${path}${qs ? `?${qs}` : \"\"}`;\n }\n\n async function request<T>(\n path: string,\n map: (body: unknown) => T,\n { method = \"GET\", body, query, signal }: RequestOptions = {},\n ): Promise<Result<T>> {\n if (!doFetch) return { ok: false, error: networkError(\"No fetch implementation available.\") };\n\n const target = url(path, query);\n const init: RequestInit = { method, headers: headers(body !== undefined) };\n if (body !== undefined) init.body = JSON.stringify(body);\n if (signal) init.signal = signal;\n\n // Only GETs are retried: a retried POST could double-submit a request or a comment.\n const attempts = method === \"GET\" ? retries + 1 : 1;\n\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n let response: Response;\n try {\n response = await doFetch(target, init);\n } catch (error) {\n if (signal?.aborted) return { ok: false, error: networkError(\"Request aborted.\") };\n if (attempt < attempts - 1) {\n await delay(retryDelayMs);\n continue;\n }\n const failure = networkError(messageOf(error));\n logError(failure);\n return { ok: false, error: failure };\n }\n\n if (response.ok) {\n const payload = await readJson(response);\n return { ok: true, data: map(payload) };\n }\n\n if (response.status >= 500 && attempt < attempts - 1) {\n await delay(retryDelayMs);\n continue;\n }\n\n const failure = errorFromResponse(response.status, await readJson(response));\n logError(failure);\n return { ok: false, error: failure };\n }\n\n return { ok: false, error: networkError() };\n }\n\n return {\n baseUrl,\n getDeviceId,\n\n getConfig: (signal) => request(\"/config\", normalizeConfig, { signal }),\n\n listFeatures: (params = {}, signal) =>\n request(\"/features\", (body) => normalizeFeaturePage(body, getDeviceId()), {\n query: {\n sort: params.sort ?? \"new\",\n limit: params.limit ?? DEFAULT_PAGE_SIZE,\n cursor: params.cursor ?? undefined,\n status: params.status?.length ? params.status.join(\",\") : undefined,\n },\n signal,\n }),\n\n getFeature: (featureId, signal) =>\n request(\n `/features/${encodeURIComponent(featureId)}`,\n (body) => normalizeFeature(body, getDeviceId()),\n { signal },\n ),\n\n submitFeature: async (input) => {\n const invalid = validateFeatureInput(input);\n if (invalid) return { ok: false, error: invalid };\n return request(\"/features\", (body) => normalizeFeature(body, getDeviceId()), {\n method: \"POST\",\n body: {\n title: input.title.trim(),\n description: (input.description ?? \"\").trim(),\n ...(input.email ? { email: input.email.trim() } : {}),\n isSubscriber: input.isSubscriber ?? false,\n },\n });\n },\n\n vote: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/vote`, normalizeVote, { method: \"POST\" }),\n\n unvote: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/vote`, normalizeVote, {\n method: \"DELETE\",\n }),\n\n follow: async (featureId, email) => {\n const invalid = validateEmail(email);\n if (invalid) return { ok: false, error: invalid };\n return request(\n `/features/${encodeURIComponent(featureId)}/follow`,\n () => ({ following: true }),\n {\n method: \"POST\",\n body: { email: email.trim() },\n },\n );\n },\n\n unfollow: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/follow`, () => ({ following: false }), {\n method: \"DELETE\",\n }),\n\n listComments: (featureId, params = {}, signal) =>\n request(\n `/features/${encodeURIComponent(featureId)}/comments`,\n (body) => normalizeCommentPage(body, getDeviceId()),\n {\n query: { limit: params.limit ?? DEFAULT_PAGE_SIZE, cursor: params.cursor ?? undefined },\n signal,\n },\n ),\n\n // The server takes the feature id in the body here; POST /features/{id}/comments is a 405.\n createComment: async (input) => {\n const invalid = validateCommentInput(input);\n if (invalid) return { ok: false, error: invalid };\n return request(\"/comments\", (body) => normalizeComment(body, getDeviceId()), {\n method: \"POST\",\n body: {\n featureId: input.featureId,\n text: input.text.trim(),\n ...(input.authorName?.trim() ? { authorName: input.authorName.trim() } : {}),\n },\n });\n },\n };\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : \"Network request failed.\";\n}\n\n// Declared, not imported from @types/node: this is a browser package, and bundlers replace\n// `process.env.NODE_ENV` inline, so the check costs nothing in a production build.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\n\nconst INVALID_KEY_HELP =\n \"The API key was rejected. Check FEWCHURS_API_KEY (server) or the apiKey passed to FewchursProvider, and that the key is active in your Fewchurs dashboard.\";\n\nconst loggedErrors = new Set<string>();\n\nfunction logError(error: FewchursError): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n const isInvalidKey = error.code === \"invalid_key\" && error.status === 401;\n const detail = isInvalidKey ? INVALID_KEY_HELP : error.message;\n if (loggedErrors.has(detail)) return;\n loggedErrors.add(detail);\n console.warn(`[fewchurs] : ${detail}`);\n}\n","const COOKIE_NAME = \"fewchurs_did\";\nconst STORAGE_KEY = \"fewchurs_device_id\";\nconst COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\nexport function createDeviceId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `fw-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nexport function readDeviceId(): string | null {\n if (typeof document === \"undefined\") return null;\n return readCookie() ?? readStorage();\n}\n\n/**\n * Cookie first, storage second: a cookie is the only copy a same-origin server can read, so\n * the board renders with the same identity on both sides and never mismatches on hydration.\n */\nexport function resolveDeviceId(): string {\n const existing = readDeviceId();\n if (existing) {\n persist(existing);\n return existing;\n }\n const created = createDeviceId();\n persist(created);\n return created;\n}\n\nfunction persist(deviceId: string): void {\n if (typeof document === \"undefined\") return;\n try {\n document.cookie = `${COOKIE_NAME}=${encodeURIComponent(deviceId)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;\n } catch {\n // Cookies can be blocked entirely; storage below is the fallback.\n }\n try {\n localStorage.setItem(STORAGE_KEY, deviceId);\n } catch {\n // Private-mode storage throws on write; the cookie or a fresh id per visit still works.\n }\n}\n\nfunction readCookie(): string | null {\n const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_NAME}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction readStorage(): string | null {\n try {\n return localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n}\n\nexport { COOKIE_NAME as DEVICE_ID_COOKIE };\n","import type { FeatureStatus, FeatureSort } from \"../types\";\n\nexport interface ListQuery {\n sort: FeatureSort;\n status?: FeatureStatus[];\n}\n\nexport function listKey({ sort, status }: ListQuery): string {\n return status?.length ? `${sort}:${[...status].sort().join(\",\")}` : sort;\n}\n\n/**\n * One feature, fetched by id, kept in `lists` beside the real lists. That way a deep-linked\n * request goes through the same vote, comment and follow updates as a row in a list.\n */\nexport function featureKey(featureId: string): string {\n return `feature:${featureId}`;\n}\n"],"mappings":"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","names":["failure"]}
|
|
1
|
+
{"version":3,"sources":["../src/client/version.ts","../src/client/constants.ts","../src/client/errors.ts","../src/client/normalize.ts","../src/client/validate.ts","../src/client/client.ts","../src/identity/deviceId.ts","../src/store/keys.ts"],"sourcesContent":["// Generated by tools/stamp-version.mjs from @fewchurs/react's package.json. Do not edit by hand.\nexport const SDK_VERSION = \"1.5.0\";\n","import type { FeatureStatus } from \"../types\";\nimport { SDK_VERSION } from \"./version\";\n\nexport { SDK_VERSION };\nexport const SDK_HEADER = `react/${SDK_VERSION}`;\nexport const DEFAULT_BASE_URL = \"https://www.fewchurs.com/api/v1\";\nexport const DEFAULT_PAGE_SIZE = 20;\n\n/** Mirrors the server's zod schemas, so the UI can reject input before a round trip. */\nexport const LIMITS = {\n titleMin: 3,\n titleMax: 100,\n descriptionMax: 1000,\n commentMin: 1,\n commentMax: 1000,\n authorNameMax: 60,\n} as const;\n\n/** Deliberately loose: the server owns the real check, this only catches obvious typos. */\nexport const EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** What the Roadmap tab asks for, and what it keeps if the server ignores the filter. */\nexport const ROADMAP_STATUSES: FeatureStatus[] = [\"planned\", \"in_progress\", \"done\"];\n","export type FewchursError =\n | { code: \"invalid_key\"; status: 401 | 403; message: string }\n | { code: \"validation_failed\"; status: 400; message: string }\n | { code: \"rate_limited\"; status: 429; message: string }\n | { code: \"not_found\"; status: 404; message: string }\n | { code: \"limit_reached\"; status: 402; message: string }\n | { code: \"server\"; status: number; message: string }\n | { code: \"network\"; message: string };\n\nexport type Result<T> = { ok: true; data: T } | { ok: false; error: FewchursError };\n\ninterface ServerErrorBody {\n error?: { code?: string; message?: string };\n}\n\nexport const NOT_CONFIGURED_MESSAGE = \"Fewchurs is not configured.\";\n\nconst USER_MESSAGES: Record<FewchursError[\"code\"], string> = {\n invalid_key: \"Invalid API key. Check the key in your Fewchurs dashboard.\",\n validation_failed: \"Invalid input.\",\n rate_limited: \"You've reached today's limit. Try again tomorrow.\",\n not_found: \"This request no longer exists.\",\n limit_reached: \"This board isn't accepting requests right now.\",\n server: \"Something went wrong. Try again.\",\n network: \"You're offline.\",\n};\n\nexport function isWrongApp(error: FewchursError): boolean {\n return error.code === \"invalid_key\" && error.status === 403;\n}\n\nexport function isNotConfigured(error: FewchursError): boolean {\n return error.code === \"server\" && error.message === NOT_CONFIGURED_MESSAGE;\n}\n\nexport function notConfiguredError(): FewchursError {\n return { code: \"server\", status: 500, message: NOT_CONFIGURED_MESSAGE };\n}\n\n/** Safe to show a visitor. Only validation and `wrong_app` use the server's own text. */\nexport function userMessage(error: FewchursError): string {\n if (error.code === \"validation_failed\" && error.message) return error.message;\n if (isWrongApp(error) && error.message) return error.message;\n return USER_MESSAGES[error.code];\n}\n\nexport function networkError(message = \"Network request failed.\"): FewchursError {\n return { code: \"network\", message };\n}\n\nexport function validationError(message: string): FewchursError {\n return { code: \"validation_failed\", status: 400, message };\n}\n\nexport function errorFromResponse(status: number, body: unknown): FewchursError {\n const code = (body as ServerErrorBody | null)?.error?.code;\n const serverMessage = (body as ServerErrorBody | null)?.error?.message;\n const message = serverMessage ?? `Request failed (${status}).`;\n\n switch (code) {\n case \"invalid_key\":\n return { code: \"invalid_key\", status: 401, message };\n case \"wrong_app\":\n return { code: \"invalid_key\", status: 403, message };\n case \"missing_device_id\":\n return { code: \"server\", status: 400, message };\n case \"validation_failed\":\n return { code: \"validation_failed\", status: 400, message: serverMessage ?? \"Invalid input.\" };\n case \"rate_limited\":\n return { code: \"rate_limited\", status: 429, message };\n case \"not_found\":\n return { code: \"not_found\", status: 404, message };\n case \"limit_reached\":\n return { code: \"limit_reached\", status: 402, message };\n default:\n return status === 401\n ? { code: \"invalid_key\", status: 401, message }\n : { code: \"server\", status, message };\n }\n}\n","import type {\n Comment,\n CommentPage,\n Feature,\n FeaturePage,\n FeatureStatus,\n FewchursConfig,\n} from \"../types\";\n\nconst KNOWN_STATUSES = new Set<string>([\"open\", \"planned\", \"in_progress\", \"done\", \"declined\"]);\n\nfunction asRecord(value: unknown): Record<string, unknown> {\n return typeof value === \"object\" && value !== null ? (value as Record<string, unknown>) : {};\n}\n\nfunction str(value: unknown, fallback = \"\"): string {\n return typeof value === \"string\" ? value : fallback;\n}\n\nfunction num(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction bool(value: unknown): boolean {\n return value === true;\n}\n\nexport function normalizeStatus(value: unknown): FeatureStatus {\n return typeof value === \"string\" && KNOWN_STATUSES.has(value)\n ? (value as FeatureStatus)\n : \"unknown\";\n}\n\n// `isMine` is the server's answer once it ships; until then the raw author device id is the\n// only signal, so it is compared here and never surfaced on the public type.\nfunction isMine(raw: Record<string, unknown>, ownerKey: string, deviceId: string | null): boolean {\n if (typeof raw.isMine === \"boolean\") return raw.isMine;\n const owner = raw[ownerKey];\n return typeof owner === \"string\" && owner.length > 0 && owner === deviceId;\n}\n\nexport function normalizeFeature(value: unknown, deviceId: string | null): Feature {\n const raw = asRecord(value);\n return {\n id: str(raw.id),\n title: str(raw.title),\n description: str(raw.description),\n status: normalizeStatus(raw.status),\n upvoteCount: num(raw.upvoteCount),\n commentCount: num(raw.commentCount),\n followerCount: num(raw.followerCount),\n hasVoted: bool(raw.hasVoted),\n isFollowing: bool(raw.isFollowing),\n isMine: isMine(raw, \"authorDeviceId\", deviceId),\n createdAt: str(raw.createdAt),\n updatedAt: str(raw.updatedAt, str(raw.createdAt)),\n };\n}\n\nexport function normalizeComment(value: unknown, deviceId: string | null): Comment {\n const raw = asRecord(value);\n return {\n id: str(raw.id),\n text: str(raw.text),\n authorName: str(raw.authorName, \"App User\"),\n isDeveloper: bool(raw.isDeveloper),\n isMine: isMine(raw, \"deviceId\", deviceId),\n createdAt: str(raw.createdAt),\n };\n}\n\nexport function normalizeFeaturePage(value: unknown, deviceId: string | null): FeaturePage {\n const raw = asRecord(value);\n const features = Array.isArray(raw.features) ? raw.features : [];\n return {\n features: features.map((feature) => normalizeFeature(feature, deviceId)),\n nextCursor: typeof raw.nextCursor === \"string\" ? raw.nextCursor : null,\n };\n}\n\nexport function normalizeCommentPage(value: unknown, deviceId: string | null): CommentPage {\n const raw = asRecord(value);\n const comments = Array.isArray(raw.comments) ? raw.comments : [];\n return {\n comments: comments.map((comment) => normalizeComment(comment, deviceId)),\n nextCursor: typeof raw.nextCursor === \"string\" ? raw.nextCursor : null,\n };\n}\n\nexport function normalizeConfig(value: unknown): FewchursConfig {\n const raw = asRecord(value);\n return {\n showBranding: raw.showBranding !== false,\n hideVoteCounts: bool(raw.hideVoteCounts),\n appName: typeof raw.appName === \"string\" && raw.appName ? raw.appName : null,\n };\n}\n\nexport function normalizeVote(value: unknown): { upvoteCount: number; hasVoted: boolean } {\n const raw = asRecord(value);\n return { upvoteCount: num(raw.upvoteCount), hasVoted: bool(raw.hasVoted) };\n}\n","import { EMAIL_PATTERN, LIMITS } from \"./constants\";\nimport { validationError, type FewchursError } from \"./errors\";\nimport type { CreateCommentInput, SubmitFeatureInput } from \"../types\";\n\nexport function validateFeatureInput(input: SubmitFeatureInput): FewchursError | null {\n const title = input.title.trim();\n if (title.length < LIMITS.titleMin || title.length > LIMITS.titleMax) {\n return validationError(`Title must be ${LIMITS.titleMin}–${LIMITS.titleMax} characters.`);\n }\n if ((input.description ?? \"\").trim().length > LIMITS.descriptionMax) {\n return validationError(`Description must be at most ${LIMITS.descriptionMax} characters.`);\n }\n if (input.email !== undefined && input.email !== \"\" && !isEmail(input.email)) {\n return validationError(\"Enter a valid email address.\");\n }\n return null;\n}\n\nexport function validateCommentInput(input: CreateCommentInput): FewchursError | null {\n const text = input.text.trim();\n if (text.length < LIMITS.commentMin || text.length > LIMITS.commentMax) {\n return validationError(`Comment must be ${LIMITS.commentMin}–${LIMITS.commentMax} characters.`);\n }\n if ((input.authorName ?? \"\").trim().length > LIMITS.authorNameMax) {\n return validationError(`Name must be at most ${LIMITS.authorNameMax} characters.`);\n }\n return null;\n}\n\nexport function validateEmail(email: string): FewchursError | null {\n return isEmail(email) ? null : validationError(\"Enter a valid email address.\");\n}\n\nfunction isEmail(email: string): boolean {\n return EMAIL_PATTERN.test(email.trim());\n}\n","import { DEFAULT_BASE_URL, DEFAULT_PAGE_SIZE, SDK_HEADER } from \"./constants\";\nimport {\n errorFromResponse,\n isNotConfigured,\n networkError,\n notConfiguredError,\n type FewchursError,\n type Result,\n} from \"./errors\";\nimport {\n normalizeComment,\n normalizeCommentPage,\n normalizeConfig,\n normalizeFeature,\n normalizeFeaturePage,\n normalizeVote,\n} from \"./normalize\";\nimport { validateCommentInput, validateEmail, validateFeatureInput } from \"./validate\";\nimport type {\n Comment,\n CommentPage,\n CreateCommentInput,\n Feature,\n FeaturePage,\n FewchursConfig,\n ListCommentsParams,\n ListFeaturesParams,\n SubmitFeatureInput,\n} from \"../types\";\n\nexport type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;\n\nexport interface FewchursClientOptions {\n /** Server-side, or in a SPA where the key is publishable. Omit when calling your own proxy. */\n apiKey?: string;\n /** `https://www.fewchurs.com/api/v1` by default, or a proxy path such as `/api/fewchurs`. */\n baseUrl?: string;\n deviceId?: string | null | (() => string | null);\n /** Sent as `Accept-Language`, so server-side copy and emails match the visitor. */\n language?: string;\n /**\n * The domain the API key is checked against, for calls made from a server. A browser reports\n * its own domain through `Origin`, which always wins, so leave this out in a SPA.\n */\n appId?: string;\n fetch?: FetchLike;\n retries?: number;\n retryDelayMs?: number;\n}\n\nexport interface FewchursClient {\n readonly baseUrl: string;\n getDeviceId(): string | null;\n getConfig(signal?: AbortSignal): Promise<Result<FewchursConfig>>;\n listFeatures(params?: ListFeaturesParams, signal?: AbortSignal): Promise<Result<FeaturePage>>;\n getFeature(featureId: string, signal?: AbortSignal): Promise<Result<Feature>>;\n submitFeature(input: SubmitFeatureInput): Promise<Result<Feature>>;\n vote(featureId: string): Promise<Result<{ upvoteCount: number; hasVoted: boolean }>>;\n unvote(featureId: string): Promise<Result<{ upvoteCount: number; hasVoted: boolean }>>;\n follow(featureId: string, email: string): Promise<Result<{ following: boolean }>>;\n unfollow(featureId: string): Promise<Result<{ following: boolean }>>;\n listComments(\n featureId: string,\n params?: ListCommentsParams,\n signal?: AbortSignal,\n ): Promise<Result<CommentPage>>;\n createComment(input: CreateCommentInput): Promise<Result<Comment>>;\n}\n\ninterface RequestOptions {\n method?: \"GET\" | \"POST\" | \"DELETE\";\n body?: unknown;\n query?: Record<string, string | number | undefined | null>;\n signal?: AbortSignal;\n}\n\nexport function createFewchursClient(options: FewchursClientOptions = {}): FewchursClient {\n const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n const doFetch = options.fetch ?? globalThis.fetch?.bind(globalThis);\n const retries = options.retries ?? 1;\n const retryDelayMs = options.retryDelayMs ?? 250;\n\n if (options.apiKey && !options.apiKey.startsWith(\"fr_\")) logOnce(BAD_KEY_PREFIX);\n\n function getDeviceId(): string | null {\n return typeof options.deviceId === \"function\" ? options.deviceId() : (options.deviceId ?? null);\n }\n\n function headers(hasBody: boolean): Record<string, string> {\n const result: Record<string, string> = {\n Accept: \"application/json\",\n \"X-Fewchurs-Sdk\": SDK_HEADER,\n };\n if (hasBody) result[\"Content-Type\"] = \"application/json\";\n if (options.apiKey) result.Authorization = `Bearer ${options.apiKey}`;\n const deviceId = getDeviceId();\n if (deviceId) result[\"X-Device-Id\"] = deviceId;\n if (options.language) result[\"Accept-Language\"] = options.language;\n if (options.appId) result[\"X-Fewchurs-App-Id\"] = options.appId;\n return result;\n }\n\n function url(path: string, query?: RequestOptions[\"query\"]): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(query ?? {})) {\n if (value !== undefined && value !== null && value !== \"\") search.set(key, String(value));\n }\n const qs = search.toString();\n return `${baseUrl}${path}${qs ? `?${qs}` : \"\"}`;\n }\n\n async function request<T>(\n path: string,\n map: (body: unknown) => T,\n { method = \"GET\", body, query, signal }: RequestOptions = {},\n ): Promise<Result<T>> {\n if (!doFetch) return { ok: false, error: networkError(\"No fetch implementation available.\") };\n if (!options.apiKey && options.baseUrl === undefined) {\n const failure = notConfiguredError();\n logError(failure);\n return { ok: false, error: failure };\n }\n\n const target = url(path, query);\n const init: RequestInit = { method, headers: headers(body !== undefined) };\n if (body !== undefined) init.body = JSON.stringify(body);\n if (signal) init.signal = signal;\n\n // Only GETs are retried: a retried POST could double-submit a request or a comment.\n const attempts = method === \"GET\" ? retries + 1 : 1;\n\n for (let attempt = 0; attempt < attempts; attempt += 1) {\n let response: Response;\n try {\n response = await doFetch(target, init);\n } catch (error) {\n if (signal?.aborted) return { ok: false, error: networkError(\"Request aborted.\") };\n if (attempt < attempts - 1) {\n await delay(retryDelayMs);\n continue;\n }\n const failure = networkError(messageOf(error));\n logError(failure);\n return { ok: false, error: failure };\n }\n\n if (response.ok) {\n const payload = await readJson(response);\n return { ok: true, data: map(payload) };\n }\n\n if (response.status >= 500 && attempt < attempts - 1) {\n await delay(retryDelayMs);\n continue;\n }\n\n const failure = errorFromResponse(response.status, await readJson(response));\n logError(failure);\n return { ok: false, error: failure };\n }\n\n return { ok: false, error: networkError() };\n }\n\n return {\n baseUrl,\n getDeviceId,\n\n getConfig: (signal) => request(\"/config\", normalizeConfig, { signal }),\n\n listFeatures: (params = {}, signal) =>\n request(\"/features\", (body) => normalizeFeaturePage(body, getDeviceId()), {\n query: {\n sort: params.sort ?? \"new\",\n limit: params.limit ?? DEFAULT_PAGE_SIZE,\n cursor: params.cursor ?? undefined,\n status: params.status?.length ? params.status.join(\",\") : undefined,\n },\n signal,\n }),\n\n getFeature: (featureId, signal) =>\n request(\n `/features/${encodeURIComponent(featureId)}`,\n (body) => normalizeFeature(body, getDeviceId()),\n { signal },\n ),\n\n submitFeature: async (input) => {\n const invalid = validateFeatureInput(input);\n if (invalid) return { ok: false, error: invalid };\n return request(\"/features\", (body) => normalizeFeature(body, getDeviceId()), {\n method: \"POST\",\n body: {\n title: input.title.trim(),\n description: (input.description ?? \"\").trim(),\n ...(input.email ? { email: input.email.trim() } : {}),\n isSubscriber: input.isSubscriber ?? false,\n },\n });\n },\n\n vote: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/vote`, normalizeVote, { method: \"POST\" }),\n\n unvote: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/vote`, normalizeVote, {\n method: \"DELETE\",\n }),\n\n follow: async (featureId, email) => {\n const invalid = validateEmail(email);\n if (invalid) return { ok: false, error: invalid };\n return request(\n `/features/${encodeURIComponent(featureId)}/follow`,\n () => ({ following: true }),\n {\n method: \"POST\",\n body: { email: email.trim() },\n },\n );\n },\n\n unfollow: (featureId) =>\n request(`/features/${encodeURIComponent(featureId)}/follow`, () => ({ following: false }), {\n method: \"DELETE\",\n }),\n\n listComments: (featureId, params = {}, signal) =>\n request(\n `/features/${encodeURIComponent(featureId)}/comments`,\n (body) => normalizeCommentPage(body, getDeviceId()),\n {\n query: { limit: params.limit ?? DEFAULT_PAGE_SIZE, cursor: params.cursor ?? undefined },\n signal,\n },\n ),\n\n // The server takes the feature id in the body here; POST /features/{id}/comments is a 405.\n createComment: async (input) => {\n const invalid = validateCommentInput(input);\n if (invalid) return { ok: false, error: invalid };\n return request(\"/comments\", (body) => normalizeComment(body, getDeviceId()), {\n method: \"POST\",\n body: {\n featureId: input.featureId,\n text: input.text.trim(),\n ...(input.authorName?.trim() ? { authorName: input.authorName.trim() } : {}),\n },\n });\n },\n };\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\nfunction delay(ms: number): Promise<void> {\n return ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : \"Network request failed.\";\n}\n\n// Declared, not imported from @types/node: this is a browser package, and bundlers replace\n// `process.env.NODE_ENV` inline, so the check costs nothing in a production build.\ndeclare const process: { env?: Record<string, string | undefined> } | undefined;\n\nconst INVALID_KEY_HELP =\n \"Fewchurs rejected the API key. Check the key in your dashboard (Apps → API keys).\";\n\nconst NOT_CONFIGURED_HELP =\n \"Fewchurs is not configured. Pass apiKey to FewchursProvider, or set FEWCHURS_API_KEY on your server.\";\n\nconst BAD_KEY_PREFIX = 'The Fewchurs API key should start with \"fr_\". Copy it from your dashboard.';\n\nconst loggedErrors = new Set<string>();\n\nfunction logOnce(detail: string): void {\n if (typeof process !== \"undefined\" && process.env?.NODE_ENV === \"production\") return;\n if (loggedErrors.has(detail)) return;\n loggedErrors.add(detail);\n console.warn(`[fewchurs] : ${detail}`);\n}\n\nfunction logError(error: FewchursError): void {\n if (isNotConfigured(error)) return logOnce(NOT_CONFIGURED_HELP);\n const isInvalidKey = error.code === \"invalid_key\" && error.status === 401;\n logOnce(isInvalidKey ? INVALID_KEY_HELP : error.message);\n}\n","const COOKIE_NAME = \"fewchurs_did\";\nconst STORAGE_KEY = \"fewchurs_device_id\";\nconst COOKIE_MAX_AGE = 60 * 60 * 24 * 365;\n\nexport function createDeviceId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID();\n }\n return `fw-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nexport function readDeviceId(): string | null {\n if (typeof document === \"undefined\") return null;\n return readCookie() ?? readStorage();\n}\n\n/**\n * Cookie first, storage second: a cookie is the only copy a same-origin server can read, so\n * the board renders with the same identity on both sides and never mismatches on hydration.\n */\nexport function resolveDeviceId(): string {\n const existing = readDeviceId();\n if (existing) {\n persist(existing);\n return existing;\n }\n const created = createDeviceId();\n persist(created);\n return created;\n}\n\nfunction persist(deviceId: string): void {\n if (typeof document === \"undefined\") return;\n try {\n document.cookie = `${COOKIE_NAME}=${encodeURIComponent(deviceId)}; path=/; max-age=${COOKIE_MAX_AGE}; SameSite=Lax`;\n } catch {\n // Cookies can be blocked entirely; storage below is the fallback.\n }\n try {\n localStorage.setItem(STORAGE_KEY, deviceId);\n } catch {\n // Private-mode storage throws on write; the cookie or a fresh id per visit still works.\n }\n}\n\nfunction readCookie(): string | null {\n const match = document.cookie.match(new RegExp(`(?:^|; )${COOKIE_NAME}=([^;]*)`));\n return match?.[1] ? decodeURIComponent(match[1]) : null;\n}\n\nfunction readStorage(): string | null {\n try {\n return localStorage.getItem(STORAGE_KEY);\n } catch {\n return null;\n }\n}\n\nexport { COOKIE_NAME as DEVICE_ID_COOKIE };\n","import type { FeatureStatus, FeatureSort } from \"../types\";\n\nexport interface ListQuery {\n sort: FeatureSort;\n status?: FeatureStatus[];\n}\n\nexport function listKey({ sort, status }: ListQuery): string {\n return status?.length ? `${sort}:${[...status].sort().join(\",\")}` : sort;\n}\n\n/**\n * One feature, fetched by id, kept in `lists` beside the real lists. That way a deep-linked\n * request goes through the same vote, comment and follow updates as a row in a list.\n */\nexport function featureKey(featureId: string): string {\n return `feature:${featureId}`;\n}\n"],"mappings":"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","names":["failure"]}
|
package/dist/index.d.mts
CHANGED
|
@@ -35,7 +35,8 @@ type Result<T> = {
|
|
|
35
35
|
ok: false;
|
|
36
36
|
error: FewchursError;
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
declare const NOT_CONFIGURED_MESSAGE = "Fewchurs is not configured.";
|
|
39
|
+
/** Safe to show a visitor. Only validation and `wrong_app` use the server's own text. */
|
|
39
40
|
declare function userMessage(error: FewchursError): string;
|
|
40
41
|
declare function networkError(message?: string): FewchursError;
|
|
41
42
|
declare function validationError(message: string): FewchursError;
|
|
@@ -267,6 +268,14 @@ interface BoardLabels {
|
|
|
267
268
|
errorWrongAppDetail: string;
|
|
268
269
|
errorTitle: string;
|
|
269
270
|
errorDetail: string;
|
|
271
|
+
errorOfflineTitle: string;
|
|
272
|
+
errorOfflineDetail: string;
|
|
273
|
+
errorNotSetUpTitle: string;
|
|
274
|
+
errorNotSetUpDetail: string;
|
|
275
|
+
errorLoadMore: string;
|
|
276
|
+
errorComments: string;
|
|
277
|
+
fieldTitleTooShort: string;
|
|
278
|
+
fieldEmailInvalid: string;
|
|
270
279
|
comments: string;
|
|
271
280
|
commentPlaceholder: string;
|
|
272
281
|
postComment: string;
|
|
@@ -319,9 +328,9 @@ declare function useBoard(): BoardContextValue;
|
|
|
319
328
|
|
|
320
329
|
declare function Branding(): React.JSX.Element | null;
|
|
321
330
|
|
|
322
|
-
declare const SDK_VERSION = "1.
|
|
331
|
+
declare const SDK_VERSION = "1.5.0";
|
|
323
332
|
|
|
324
|
-
declare const SDK_HEADER = "react/1.
|
|
333
|
+
declare const SDK_HEADER = "react/1.5.0";
|
|
325
334
|
declare const DEFAULT_BASE_URL = "https://www.fewchurs.com/api/v1";
|
|
326
335
|
declare const DEFAULT_PAGE_SIZE = 20;
|
|
327
336
|
/** Mirrors the server's zod schemas, so the UI can reject input before a round trip. */
|
|
@@ -477,6 +486,7 @@ interface UseCommentsResult {
|
|
|
477
486
|
submitError: FewchursError | null;
|
|
478
487
|
hasMore: boolean;
|
|
479
488
|
loadMore: () => Promise<void>;
|
|
489
|
+
refresh: () => Promise<void>;
|
|
480
490
|
add: (text: string, authorName?: string) => Promise<boolean>;
|
|
481
491
|
}
|
|
482
492
|
declare function useComments(featureId: string, { enabled }?: UseCommentsOptions): UseCommentsResult;
|
|
@@ -541,4 +551,4 @@ declare function validateFeatureInput(input: SubmitFeatureInput): FewchursError
|
|
|
541
551
|
declare function validateCommentInput(input: CreateCommentInput): FewchursError | null;
|
|
542
552
|
declare function validateEmail(email: string): FewchursError | null;
|
|
543
553
|
|
|
544
|
-
export { type BoardContextValue, type BoardLabels, BoardRoot, type BoardTab, BoardTabs, type BoardTheme, Branding, type Comment, CommentForm, CommentList, type CommentPage, type CommentsState, type CreateCommentInput, DEFAULT_BASE_URL, DEFAULT_LABELS, DEFAULT_PAGE_SIZE, COOKIE_NAME as DEVICE_ID_COOKIE, type EmailFieldMode, type EventHandler, type Feature, type FeatureActions, FeatureCard, FeatureList, type FeaturePage, type FeatureSort, type FeatureStatus, type FetchLike, FewchursBoard, FewchursBoardDialog, type FewchursBoardDialogProps, type FewchursBoardProps, FewchursButton, type FewchursButtonProps, type FewchursClient, type FewchursClientOptions, type FewchursConfig, type FewchursContextValue, type FewchursError, type FewchursEvent, type FewchursEventName, FewchursProvider, type FewchursProviderProps, FollowForm, LIMITS, type ListCommentsParams, type ListFeaturesParams, type ListQuery, type ListState, type LoadStatus, ROADMAP_STATUSES, type Result, SDK_HEADER, SDK_VERSION, StatusBadge, type Store, type StoreOptions, type StoreState, type SubmitFeatureInput, SubmitForm, SubmitTrigger, type UseCommentsOptions, type UseCommentsResult, type UseFeatureOptions, type UseFeatureResult, type UseFeaturesOptions, type UseFeaturesResult, type UseFewchursConfigResult, type UseFollowResult, type UseSubmitFeatureResult, type UseVoteResult, VoteButton, commentsStateOf, createDeviceId, createFewchursClient, createStore, errorFromResponse, featureKey, listKey, listStateOf, mergeLabels, networkError, normalizeComment, normalizeCommentPage, normalizeConfig, normalizeFeature, normalizeFeaturePage, normalizeStatus, normalizeVote, readDeviceId, resolveDeviceId, useBoard, useComments, useFeature, useFeatures, useFewchurs, useFewchursConfig, useFollow, useSubmitFeature, useVote, userMessage, validateCommentInput, validateEmail, validateFeatureInput, validationError };
|
|
554
|
+
export { type BoardContextValue, type BoardLabels, BoardRoot, type BoardTab, BoardTabs, type BoardTheme, Branding, type Comment, CommentForm, CommentList, type CommentPage, type CommentsState, type CreateCommentInput, DEFAULT_BASE_URL, DEFAULT_LABELS, DEFAULT_PAGE_SIZE, COOKIE_NAME as DEVICE_ID_COOKIE, type EmailFieldMode, type EventHandler, type Feature, type FeatureActions, FeatureCard, FeatureList, type FeaturePage, type FeatureSort, type FeatureStatus, type FetchLike, FewchursBoard, FewchursBoardDialog, type FewchursBoardDialogProps, type FewchursBoardProps, FewchursButton, type FewchursButtonProps, type FewchursClient, type FewchursClientOptions, type FewchursConfig, type FewchursContextValue, type FewchursError, type FewchursEvent, type FewchursEventName, FewchursProvider, type FewchursProviderProps, FollowForm, LIMITS, type ListCommentsParams, type ListFeaturesParams, type ListQuery, type ListState, type LoadStatus, NOT_CONFIGURED_MESSAGE, ROADMAP_STATUSES, type Result, SDK_HEADER, SDK_VERSION, StatusBadge, type Store, type StoreOptions, type StoreState, type SubmitFeatureInput, SubmitForm, SubmitTrigger, type UseCommentsOptions, type UseCommentsResult, type UseFeatureOptions, type UseFeatureResult, type UseFeaturesOptions, type UseFeaturesResult, type UseFewchursConfigResult, type UseFollowResult, type UseSubmitFeatureResult, type UseVoteResult, VoteButton, commentsStateOf, createDeviceId, createFewchursClient, createStore, errorFromResponse, featureKey, listKey, listStateOf, mergeLabels, networkError, normalizeComment, normalizeCommentPage, normalizeConfig, normalizeFeature, normalizeFeaturePage, normalizeStatus, normalizeVote, readDeviceId, resolveDeviceId, useBoard, useComments, useFeature, useFeatures, useFewchurs, useFewchursConfig, useFollow, useSubmitFeature, useVote, userMessage, validateCommentInput, validateEmail, validateFeatureInput, validationError };
|
package/dist/index.d.ts
CHANGED
|
@@ -35,7 +35,8 @@ type Result<T> = {
|
|
|
35
35
|
ok: false;
|
|
36
36
|
error: FewchursError;
|
|
37
37
|
};
|
|
38
|
-
|
|
38
|
+
declare const NOT_CONFIGURED_MESSAGE = "Fewchurs is not configured.";
|
|
39
|
+
/** Safe to show a visitor. Only validation and `wrong_app` use the server's own text. */
|
|
39
40
|
declare function userMessage(error: FewchursError): string;
|
|
40
41
|
declare function networkError(message?: string): FewchursError;
|
|
41
42
|
declare function validationError(message: string): FewchursError;
|
|
@@ -267,6 +268,14 @@ interface BoardLabels {
|
|
|
267
268
|
errorWrongAppDetail: string;
|
|
268
269
|
errorTitle: string;
|
|
269
270
|
errorDetail: string;
|
|
271
|
+
errorOfflineTitle: string;
|
|
272
|
+
errorOfflineDetail: string;
|
|
273
|
+
errorNotSetUpTitle: string;
|
|
274
|
+
errorNotSetUpDetail: string;
|
|
275
|
+
errorLoadMore: string;
|
|
276
|
+
errorComments: string;
|
|
277
|
+
fieldTitleTooShort: string;
|
|
278
|
+
fieldEmailInvalid: string;
|
|
270
279
|
comments: string;
|
|
271
280
|
commentPlaceholder: string;
|
|
272
281
|
postComment: string;
|
|
@@ -319,9 +328,9 @@ declare function useBoard(): BoardContextValue;
|
|
|
319
328
|
|
|
320
329
|
declare function Branding(): React.JSX.Element | null;
|
|
321
330
|
|
|
322
|
-
declare const SDK_VERSION = "1.
|
|
331
|
+
declare const SDK_VERSION = "1.5.0";
|
|
323
332
|
|
|
324
|
-
declare const SDK_HEADER = "react/1.
|
|
333
|
+
declare const SDK_HEADER = "react/1.5.0";
|
|
325
334
|
declare const DEFAULT_BASE_URL = "https://www.fewchurs.com/api/v1";
|
|
326
335
|
declare const DEFAULT_PAGE_SIZE = 20;
|
|
327
336
|
/** Mirrors the server's zod schemas, so the UI can reject input before a round trip. */
|
|
@@ -477,6 +486,7 @@ interface UseCommentsResult {
|
|
|
477
486
|
submitError: FewchursError | null;
|
|
478
487
|
hasMore: boolean;
|
|
479
488
|
loadMore: () => Promise<void>;
|
|
489
|
+
refresh: () => Promise<void>;
|
|
480
490
|
add: (text: string, authorName?: string) => Promise<boolean>;
|
|
481
491
|
}
|
|
482
492
|
declare function useComments(featureId: string, { enabled }?: UseCommentsOptions): UseCommentsResult;
|
|
@@ -541,4 +551,4 @@ declare function validateFeatureInput(input: SubmitFeatureInput): FewchursError
|
|
|
541
551
|
declare function validateCommentInput(input: CreateCommentInput): FewchursError | null;
|
|
542
552
|
declare function validateEmail(email: string): FewchursError | null;
|
|
543
553
|
|
|
544
|
-
export { type BoardContextValue, type BoardLabels, BoardRoot, type BoardTab, BoardTabs, type BoardTheme, Branding, type Comment, CommentForm, CommentList, type CommentPage, type CommentsState, type CreateCommentInput, DEFAULT_BASE_URL, DEFAULT_LABELS, DEFAULT_PAGE_SIZE, COOKIE_NAME as DEVICE_ID_COOKIE, type EmailFieldMode, type EventHandler, type Feature, type FeatureActions, FeatureCard, FeatureList, type FeaturePage, type FeatureSort, type FeatureStatus, type FetchLike, FewchursBoard, FewchursBoardDialog, type FewchursBoardDialogProps, type FewchursBoardProps, FewchursButton, type FewchursButtonProps, type FewchursClient, type FewchursClientOptions, type FewchursConfig, type FewchursContextValue, type FewchursError, type FewchursEvent, type FewchursEventName, FewchursProvider, type FewchursProviderProps, FollowForm, LIMITS, type ListCommentsParams, type ListFeaturesParams, type ListQuery, type ListState, type LoadStatus, ROADMAP_STATUSES, type Result, SDK_HEADER, SDK_VERSION, StatusBadge, type Store, type StoreOptions, type StoreState, type SubmitFeatureInput, SubmitForm, SubmitTrigger, type UseCommentsOptions, type UseCommentsResult, type UseFeatureOptions, type UseFeatureResult, type UseFeaturesOptions, type UseFeaturesResult, type UseFewchursConfigResult, type UseFollowResult, type UseSubmitFeatureResult, type UseVoteResult, VoteButton, commentsStateOf, createDeviceId, createFewchursClient, createStore, errorFromResponse, featureKey, listKey, listStateOf, mergeLabels, networkError, normalizeComment, normalizeCommentPage, normalizeConfig, normalizeFeature, normalizeFeaturePage, normalizeStatus, normalizeVote, readDeviceId, resolveDeviceId, useBoard, useComments, useFeature, useFeatures, useFewchurs, useFewchursConfig, useFollow, useSubmitFeature, useVote, userMessage, validateCommentInput, validateEmail, validateFeatureInput, validationError };
|
|
554
|
+
export { type BoardContextValue, type BoardLabels, BoardRoot, type BoardTab, BoardTabs, type BoardTheme, Branding, type Comment, CommentForm, CommentList, type CommentPage, type CommentsState, type CreateCommentInput, DEFAULT_BASE_URL, DEFAULT_LABELS, DEFAULT_PAGE_SIZE, COOKIE_NAME as DEVICE_ID_COOKIE, type EmailFieldMode, type EventHandler, type Feature, type FeatureActions, FeatureCard, FeatureList, type FeaturePage, type FeatureSort, type FeatureStatus, type FetchLike, FewchursBoard, FewchursBoardDialog, type FewchursBoardDialogProps, type FewchursBoardProps, FewchursButton, type FewchursButtonProps, type FewchursClient, type FewchursClientOptions, type FewchursConfig, type FewchursContextValue, type FewchursError, type FewchursEvent, type FewchursEventName, FewchursProvider, type FewchursProviderProps, FollowForm, LIMITS, type ListCommentsParams, type ListFeaturesParams, type ListQuery, type ListState, type LoadStatus, NOT_CONFIGURED_MESSAGE, ROADMAP_STATUSES, type Result, SDK_HEADER, SDK_VERSION, StatusBadge, type Store, type StoreOptions, type StoreState, type SubmitFeatureInput, SubmitForm, SubmitTrigger, type UseCommentsOptions, type UseCommentsResult, type UseFeatureOptions, type UseFeatureResult, type UseFeaturesOptions, type UseFeaturesResult, type UseFewchursConfigResult, type UseFollowResult, type UseSubmitFeatureResult, type UseVoteResult, VoteButton, commentsStateOf, createDeviceId, createFewchursClient, createStore, errorFromResponse, featureKey, listKey, listStateOf, mergeLabels, networkError, normalizeComment, normalizeCommentPage, normalizeConfig, normalizeFeature, normalizeFeaturePage, normalizeStatus, normalizeVote, readDeviceId, resolveDeviceId, useBoard, useComments, useFeature, useFeatures, useFewchurs, useFewchursConfig, useFollow, useSubmitFeature, useVote, userMessage, validateCommentInput, validateEmail, validateFeatureInput, validationError };
|