mirror of
https://github.com/jnsahaj/tweakcn.git
synced 2026-08-28 23:02:07 +08:00
249b72f98a
* add community themes feature Allow users to publish saved themes to a public community page, browse/sort community themes, and like them with auth-gated interactions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * improve community themes UI and fix like state - Polish community page with better layout, sort icons, loading skeletons - Show author, date, like count on theme detail page - Fix isLikedByMe not being fetched in getCommunityDataForTheme - Remove Community link from app header - Replace Save with Publish button in editor action bar for saved themes - Include isPublished in getThemes() via server-side join to avoid layout shift Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * ui --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
2.1 KiB
TypeScript
90 lines
2.1 KiB
TypeScript
import z from "zod";
|
|
|
|
// Error codes for server actions - these survive serialization
|
|
export const ErrorCode = {
|
|
UNAUTHORIZED: "UNAUTHORIZED",
|
|
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
THEME_NOT_FOUND: "THEME_NOT_FOUND",
|
|
THEME_LIMIT_REACHED: "THEME_LIMIT_REACHED",
|
|
ALREADY_PUBLISHED: "ALREADY_PUBLISHED",
|
|
UNKNOWN_ERROR: "UNKNOWN_ERROR",
|
|
} as const;
|
|
|
|
export type ErrorCode = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
|
|
// Typed result for server actions
|
|
export type ActionResult<T> =
|
|
| { success: true; data: T }
|
|
| { success: false; error: { code: ErrorCode; message: string } };
|
|
|
|
// Helper to create error results
|
|
export function actionError(code: ErrorCode, message: string): ActionResult<never> {
|
|
return { success: false, error: { code, message } };
|
|
}
|
|
|
|
// Helper to create success results
|
|
export function actionSuccess<T>(data: T): ActionResult<T> {
|
|
return { success: true, data };
|
|
}
|
|
|
|
export class UnauthorizedError extends Error {
|
|
constructor(message = "Unauthorized") {
|
|
super(message);
|
|
this.name = "UnauthorizedError";
|
|
}
|
|
}
|
|
|
|
export class ValidationError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public details?: unknown
|
|
) {
|
|
super(message);
|
|
this.name = "ValidationError";
|
|
}
|
|
}
|
|
|
|
export class SubscriptionRequiredError extends Error {
|
|
constructor(
|
|
message = "Subscription required",
|
|
public data?: unknown
|
|
) {
|
|
super(message);
|
|
this.name = "SubscriptionRequiredError";
|
|
}
|
|
}
|
|
|
|
export class ThemeNotFoundError extends Error {
|
|
constructor(message = "Theme not found") {
|
|
super(message);
|
|
this.name = "ThemeNotFoundError";
|
|
}
|
|
}
|
|
|
|
export type ApiErrorCode =
|
|
| "SUBSCRIPTION_REQUIRED"
|
|
| "VALIDATION_ERROR"
|
|
| "UNAUTHORIZED"
|
|
| "UNKNOWN_ERROR";
|
|
|
|
export class ApiError extends Error {
|
|
constructor(
|
|
public code: ApiErrorCode,
|
|
message: string,
|
|
public data?: unknown,
|
|
public status?: number
|
|
) {
|
|
super(message);
|
|
this.name = "ApiError";
|
|
}
|
|
}
|
|
|
|
export const MyErrorResponseSchema = z.object({
|
|
code: z.string().optional(),
|
|
message: z.string().optional(),
|
|
data: z.unknown().optional(),
|
|
status: z.number().optional(),
|
|
});
|
|
|
|
export type MyErrorResponseType = z.infer<typeof MyErrorResponseSchema>;
|