create-stackkit-app 0.4.2 → 0.4.3

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.
Files changed (59) hide show
  1. package/README.md +1 -0
  2. package/bin/create-stackkit.js +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/lib/create-project.js +329 -143
  5. package/dist/lib/template-composer.js +21 -21
  6. package/modules/auth/better-auth-express/adapters/mongoose-mongodb.ts +3 -3
  7. package/modules/auth/better-auth-express/adapters/prisma-mongodb.ts +4 -4
  8. package/modules/auth/better-auth-express/adapters/prisma-postgresql.ts +4 -4
  9. package/modules/auth/better-auth-express/files/lib/auth.ts +1 -1
  10. package/modules/auth/better-auth-express/files/routes/auth.ts +3 -3
  11. package/modules/auth/better-auth-nextjs/adapters/mongoose-mongodb.ts +3 -3
  12. package/modules/auth/better-auth-nextjs/adapters/prisma-mongodb.ts +4 -4
  13. package/modules/auth/better-auth-nextjs/adapters/prisma-postgresql.ts +4 -4
  14. package/modules/auth/better-auth-nextjs/files/api/auth/[...all]/route.ts +2 -3
  15. package/modules/auth/better-auth-nextjs/files/lib/auth.ts +4 -4
  16. package/modules/auth/better-auth-react/files/lib/auth-client.ts +2 -2
  17. package/modules/auth/clerk-express/files/lib/auth.ts +1 -1
  18. package/modules/auth/clerk-nextjs/files/lib/auth-provider.tsx +1 -1
  19. package/modules/auth/clerk-nextjs/files/middleware.ts +3 -3
  20. package/modules/auth/clerk-react/files/lib/auth-provider.tsx +2 -2
  21. package/modules/database/mongoose-mongodb/files/lib/db.ts +3 -3
  22. package/modules/database/prisma-mongodb/files/lib/db.ts +2 -2
  23. package/modules/database/prisma-postgresql/files/lib/db.ts +2 -2
  24. package/package.json +8 -3
  25. package/templates/express/package.json +1 -0
  26. package/templates/express/src/app.ts +17 -15
  27. package/templates/express/src/config/env.ts +8 -8
  28. package/templates/express/src/middlewares/error.middleware.ts +3 -3
  29. package/templates/express/src/server.ts +2 -2
  30. package/templates/express/template.json +38 -1
  31. package/templates/express/tsconfig.json +17 -0
  32. package/templates/nextjs/app/layout.tsx +1 -5
  33. package/templates/nextjs/app/page.tsx +26 -34
  34. package/templates/nextjs/package.json +2 -1
  35. package/templates/nextjs/template.json +13 -1
  36. package/templates/react-vite/eslint.config.js +9 -9
  37. package/templates/react-vite/package.json +1 -2
  38. package/templates/react-vite/src/api/client.ts +16 -16
  39. package/templates/react-vite/src/api/services/user.service.ts +2 -10
  40. package/templates/react-vite/src/components/ErrorBoundary.tsx +4 -4
  41. package/templates/react-vite/src/components/Layout.tsx +1 -1
  42. package/templates/react-vite/src/components/Loading.tsx +1 -1
  43. package/templates/react-vite/src/components/SEO.tsx +5 -5
  44. package/templates/react-vite/src/config/constants.ts +3 -3
  45. package/templates/react-vite/src/hooks/index.ts +5 -5
  46. package/templates/react-vite/src/lib/queryClient.ts +2 -2
  47. package/templates/react-vite/src/main.tsx +12 -12
  48. package/templates/react-vite/src/pages/About.tsx +6 -2
  49. package/templates/react-vite/src/pages/Home.tsx +8 -4
  50. package/templates/react-vite/src/pages/NotFound.tsx +2 -2
  51. package/templates/react-vite/src/pages/UserProfile.tsx +6 -6
  52. package/templates/react-vite/src/router.tsx +13 -13
  53. package/templates/react-vite/src/types/{api.ts → api.d.ts} +6 -6
  54. package/templates/react-vite/src/types/user.d.ts +6 -0
  55. package/templates/react-vite/src/utils/helpers.ts +11 -11
  56. package/templates/react-vite/src/utils/storage.ts +4 -4
  57. package/templates/react-vite/template.json +26 -0
  58. package/templates/react-vite/tsconfig.json +1 -4
  59. package/templates/react-vite/vite.config.ts +5 -5
@@ -4,59 +4,51 @@ export default function Home() {
4
4
  return (
5
5
  <div className="flex min-h-screen items-center justify-center bg-zinc-50 font-sans dark:bg-black">
6
6
  <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-white dark:bg-black sm:items-start">
7
- <Image
8
- className="dark:invert"
9
- src="/next.svg"
10
- alt="Next.js logo"
11
- width={100}
12
- height={20}
13
- priority
14
- />
7
+ <div className="flex items-center gap-4 mb-8">
8
+ <div className="text-2xl font-bold text-black dark:text-white">Stackkit</div>
9
+ <span className="text-xl text-zinc-400">+</span>
10
+ <Image
11
+ className="dark:invert"
12
+ src="/next.svg"
13
+ alt="Next.js logo"
14
+ width={100}
15
+ height={20}
16
+ priority
17
+ />
18
+ </div>
15
19
  <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
16
20
  <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-black dark:text-zinc-50">
17
21
  To get started, edit the page.tsx file.
18
22
  </h1>
19
23
  <p className="max-w-md text-lg leading-8 text-zinc-600 dark:text-zinc-400">
20
- Looking for a starting point or more instructions? Head over to{' '}
21
- <a
22
- href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
23
- className="font-medium text-zinc-950 dark:text-zinc-50"
24
- >
25
- Templates
26
- </a>{' '}
27
- or the{' '}
24
+ This template includes Next.js, Tailwind CSS, and Stackkit best practices. Check out the{" "}
28
25
  <a
29
- href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
30
- className="font-medium text-zinc-950 dark:text-zinc-50"
26
+ href="https://github.com/tariqul420/stackkit"
27
+ className="font-medium text-zinc-950 dark:text-zinc-50 hover:underline"
28
+ target="_blank"
29
+ rel="noopener noreferrer"
31
30
  >
32
- Learning
33
- </a>{' '}
34
- center.
31
+ Stackkit repository
32
+ </a>{" "}
33
+ for more info.
35
34
  </p>
36
35
  </div>
37
36
  <div className="flex flex-col gap-4 text-base font-medium sm:flex-row">
38
37
  <a
39
- className="flex h-12 w-full items-center justify-center gap-2 rounded-full bg-foreground px-5 text-background transition-colors hover:bg-[#383838] dark:hover:bg-[#ccc] md:w-[158px]"
40
- href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
38
+ className="flex h-12 w-full items-center justify-center rounded-full bg-white text-black px-5 transition-colors hover:bg-zinc-200 md:w-40"
39
+ href="https://nextjs.org/docs"
41
40
  target="_blank"
42
41
  rel="noopener noreferrer"
43
42
  >
44
- <Image
45
- className="dark:invert"
46
- src="/vercel.svg"
47
- alt="Vercel logomark"
48
- width={16}
49
- height={16}
50
- />
51
- Deploy Now
43
+ Documentation
52
44
  </a>
53
45
  <a
54
- className="flex h-12 w-full items-center justify-center rounded-full border border-solid border-black/8 px-5 transition-colors hover:border-transparent hover:bg-black/4 dark:border-white/[.145] dark:hover:bg-[#1a1a1a] md:w-[158px]"
55
- href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
46
+ className="flex h-12 w-full items-center justify-center rounded-full bg-black text-white px-5 transition-colors hover:bg-zinc-900 md:w-40"
47
+ href="https://github.com/tariqul420/stackkit"
56
48
  target="_blank"
57
49
  rel="noopener noreferrer"
58
50
  >
59
- Documentation
51
+ Stackkit GitHub
60
52
  </a>
61
53
  </div>
62
54
  </main>
@@ -2,6 +2,7 @@
2
2
  "name": "my-app",
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
+ "type": "module",
5
6
  "scripts": {
6
7
  "dev": "next dev",
7
8
  "build": "next build",
@@ -23,4 +24,4 @@
23
24
  "tailwindcss": "^4",
24
25
  "typescript": "^5"
25
26
  }
26
- }
27
+ }
@@ -14,5 +14,17 @@
14
14
  "postcss.config.mjs",
15
15
  "README.md",
16
16
  "tsconfig.json"
17
- ]
17
+ ],
18
+ "scripts": {
19
+ "dev": "next dev",
20
+ "build": "next build",
21
+ "start": "next start",
22
+ "lint": "eslint"
23
+ },
24
+ "jsScripts": {
25
+ "dev": "next dev",
26
+ "build": "next build",
27
+ "start": "next start",
28
+ "lint": "eslint"
29
+ }
18
30
  }
@@ -1,14 +1,14 @@
1
- import js from '@eslint/js'
2
- import globals from 'globals'
3
- import reactHooks from 'eslint-plugin-react-hooks'
4
- import reactRefresh from 'eslint-plugin-react-refresh'
5
- import tseslint from 'typescript-eslint'
6
- import { defineConfig, globalIgnores } from 'eslint/config'
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import reactRefresh from "eslint-plugin-react-refresh";
5
+ import tseslint from "typescript-eslint";
6
+ import { defineConfig, globalIgnores } from "eslint/config";
7
7
 
8
8
  export default defineConfig([
9
- globalIgnores(['dist']),
9
+ globalIgnores(["dist"]),
10
10
  {
11
- files: ['**/*.{ts,tsx}'],
11
+ files: ["**/*.{ts,tsx}"],
12
12
  extends: [
13
13
  js.configs.recommended,
14
14
  tseslint.configs.recommended,
@@ -20,4 +20,4 @@ export default defineConfig([
20
20
  globals: globals.browser,
21
21
  },
22
22
  },
23
- ])
23
+ ]);
@@ -8,8 +8,7 @@
8
8
  "build": "tsc -b && vite build",
9
9
  "lint": "eslint .",
10
10
  "lint:fix": "eslint . --fix",
11
- "preview": "vite preview",
12
- "type-check": "tsc --noEmit"
11
+ "preview": "vite preview"
13
12
  },
14
13
  "dependencies": {
15
14
  "@tailwindcss/vite": "^4.1.18",
@@ -1,18 +1,18 @@
1
- import type { AxiosResponse } from 'axios';
2
- import axios, { AxiosError } from 'axios';
3
- import toast from 'react-hot-toast';
1
+ import type { AxiosResponse } from "axios";
2
+ import axios, { AxiosError } from "axios";
3
+ import toast from "react-hot-toast";
4
4
 
5
5
  const api = axios.create({
6
- baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api',
6
+ baseURL: import.meta.env.VITE_API_URL || "http://localhost:3000/api",
7
7
  timeout: 10000,
8
8
  headers: {
9
- 'Content-Type': 'application/json',
9
+ "Content-Type": "application/json",
10
10
  },
11
11
  });
12
12
 
13
13
  api.interceptors.request.use(
14
14
  (config) => {
15
- const token = localStorage.getItem('auth_token');
15
+ const token = localStorage.getItem("auth_token");
16
16
  if (token) {
17
17
  config.headers.Authorization = `Bearer ${token}`;
18
18
  }
@@ -20,28 +20,28 @@ api.interceptors.request.use(
20
20
  },
21
21
  (error: AxiosError) => {
22
22
  return Promise.reject(error);
23
- }
23
+ },
24
24
  );
25
25
 
26
26
  api.interceptors.response.use(
27
27
  (response: AxiosResponse) => response,
28
28
  (error: AxiosError) => {
29
29
  if (error.response?.status === 401) {
30
- localStorage.removeItem('auth_token');
31
- toast.error('Session expired. Please login again.');
30
+ localStorage.removeItem("auth_token");
31
+ toast.error("Session expired. Please login again.");
32
32
  } else if (error.response?.status === 403) {
33
- toast.error('You do not have permission to perform this action.');
33
+ toast.error("You do not have permission to perform this action.");
34
34
  } else if (error.response?.status === 404) {
35
- toast.error('Resource not found.');
35
+ toast.error("Resource not found.");
36
36
  } else if (error.response?.status === 500) {
37
- toast.error('Server error. Please try again later.');
38
- } else if (error.code === 'ECONNABORTED') {
39
- toast.error('Request timeout. Please try again.');
37
+ toast.error("Server error. Please try again later.");
38
+ } else if (error.code === "ECONNABORTED") {
39
+ toast.error("Request timeout. Please try again.");
40
40
  } else if (!error.response) {
41
- toast.error('Network error. Please check your connection.');
41
+ toast.error("Network error. Please check your connection.");
42
42
  }
43
43
  return Promise.reject(error);
44
- }
44
+ },
45
45
  );
46
46
 
47
47
  export default api;
@@ -1,12 +1,4 @@
1
- import type { ApiResponse } from '../../types/api';
2
- import api from '../client';
3
-
4
- export interface User {
5
- id: string;
6
- name: string;
7
- email: string;
8
- avatar?: string;
9
- }
1
+ import api from "../client";
10
2
 
11
3
  export const userService = {
12
4
  getUser: async (id: string): Promise<User> => {
@@ -15,7 +7,7 @@ export const userService = {
15
7
  },
16
8
 
17
9
  getCurrentUser: async (): Promise<User> => {
18
- const response = await api.get<ApiResponse<User>>('/users/me');
10
+ const response = await api.get<ApiResponse<User>>("/users/me");
19
11
  return response.data.data;
20
12
  },
21
13
 
@@ -1,5 +1,5 @@
1
- import type { ErrorInfo, ReactNode } from 'react';
2
- import { Component } from 'react';
1
+ import type { ErrorInfo, ReactNode } from "react";
2
+ import { Component } from "react";
3
3
 
4
4
  interface Props {
5
5
  children?: ReactNode;
@@ -22,7 +22,7 @@ export class ErrorBoundary extends Component<Props, State> {
22
22
  }
23
23
 
24
24
  componentDidCatch(error: Error, errorInfo: ErrorInfo) {
25
- console.error('ErrorBoundary caught an error:', error, errorInfo);
25
+ console.error("ErrorBoundary caught an error:", error, errorInfo);
26
26
  }
27
27
 
28
28
  render() {
@@ -34,7 +34,7 @@ export class ErrorBoundary extends Component<Props, State> {
34
34
  <div className="p-8 text-center">
35
35
  <h2 className="text-2xl font-semibold">Something went wrong</h2>
36
36
  <p className="mt-2 text-gray-600">
37
- {this.state.error?.message || 'An unexpected error occurred'}
37
+ {this.state.error?.message || "An unexpected error occurred"}
38
38
  </p>
39
39
  <button
40
40
  onClick={() => this.setState({ hasError: false, error: undefined })}
@@ -1,4 +1,4 @@
1
- import { Outlet } from 'react-router';
1
+ import { Outlet } from "react-router";
2
2
 
3
3
  export function Layout() {
4
4
  return (
@@ -1,4 +1,4 @@
1
- export default function Loading({ message = 'Loading...' }: { message?: string }) {
1
+ export default function Loading({ message = "Loading..." }: { message?: string }) {
2
2
  return (
3
3
  <div className="loading-container">
4
4
  <div className="loading-spinner"></div>
@@ -1,4 +1,4 @@
1
- import { Helmet, HelmetProvider } from 'react-helmet-async';
1
+ import { Helmet, HelmetProvider } from "react-helmet-async";
2
2
 
3
3
  interface SEOProps {
4
4
  title?: string;
@@ -9,10 +9,10 @@ interface SEOProps {
9
9
  }
10
10
 
11
11
  const defaultSEO = {
12
- title: 'React App',
13
- description: 'A modern React application built with Vite',
14
- keywords: 'react, vite, typescript, spa',
15
- image: '/og-image.png',
12
+ title: "React App",
13
+ description: "A modern React application built with Vite",
14
+ keywords: "react, vite, typescript, spa",
15
+ image: "/og-image.png",
16
16
  };
17
17
 
18
18
  export function SEOProvider({ children }: { children: React.ReactNode }) {
@@ -1,5 +1,5 @@
1
- export const APP_NAME = import.meta.env.VITE_APP_NAME || 'React App';
2
- export const APP_VERSION = import.meta.env.VITE_APP_VERSION || '1.0.0';
3
- export const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000/api';
1
+ export const APP_NAME = import.meta.env.VITE_APP_NAME || "React App";
2
+ export const APP_VERSION = import.meta.env.VITE_APP_VERSION || "1.0.0";
3
+ export const API_URL = import.meta.env.VITE_API_URL || "http://localhost:3000/api";
4
4
  export const IS_DEV = import.meta.env.DEV;
5
5
  export const IS_PROD = import.meta.env.PROD;
@@ -1,4 +1,4 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useState } from "react";
2
2
 
3
3
  export function useDebounce<T>(value: T, delay: number): T {
4
4
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
@@ -18,7 +18,7 @@ export function useDebounce<T>(value: T, delay: number): T {
18
18
 
19
19
  export function useLocalStorage<T>(
20
20
  key: string,
21
- initialValue: T
21
+ initialValue: T,
22
22
  ): [T, (value: T | ((val: T) => T)) => void] {
23
23
  const [storedValue, setStoredValue] = useState<T>(() => {
24
24
  try {
@@ -45,7 +45,7 @@ export function useLocalStorage<T>(
45
45
 
46
46
  export function useMediaQuery(query: string): boolean {
47
47
  const [matches, setMatches] = useState(() => {
48
- if (typeof window !== 'undefined') {
48
+ if (typeof window !== "undefined") {
49
49
  return window.matchMedia(query).matches;
50
50
  }
51
51
  return false;
@@ -55,9 +55,9 @@ export function useMediaQuery(query: string): boolean {
55
55
  const media = window.matchMedia(query);
56
56
 
57
57
  const listener = () => setMatches(media.matches);
58
- media.addEventListener('change', listener);
58
+ media.addEventListener("change", listener);
59
59
 
60
- return () => media.removeEventListener('change', listener);
60
+ return () => media.removeEventListener("change", listener);
61
61
  }, [query]);
62
62
 
63
63
  return matches;
@@ -1,4 +1,4 @@
1
- import { QueryClient } from '@tanstack/react-query';
1
+ import { QueryClient } from "@tanstack/react-query";
2
2
 
3
3
  export const queryClient = new QueryClient({
4
4
  defaultOptions: {
@@ -9,4 +9,4 @@ export const queryClient = new QueryClient({
9
9
  refetchOnWindowFocus: false,
10
10
  },
11
11
  },
12
- });
12
+ });
@@ -1,15 +1,15 @@
1
- import { QueryClientProvider } from '@tanstack/react-query';
2
- import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
3
- import { StrictMode } from 'react';
4
- import { createRoot } from 'react-dom/client';
5
- import { Toaster } from 'react-hot-toast';
6
- import { RouterProvider } from 'react-router';
7
- import { SEOProvider } from './components/SEO';
8
- import './index.css';
9
- import { queryClient } from './lib/queryClient';
10
- import { router } from './router';
1
+ import { QueryClientProvider } from "@tanstack/react-query";
2
+ import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
3
+ import { StrictMode } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import { Toaster } from "react-hot-toast";
6
+ import { RouterProvider } from "react-router";
7
+ import { SEOProvider } from "./components/SEO";
8
+ import "./index.css";
9
+ import { queryClient } from "./lib/queryClient";
10
+ import { router } from "./router";
11
11
 
12
- createRoot(document.getElementById('root')!).render(
12
+ createRoot(document.getElementById("root")!).render(
13
13
  <StrictMode>
14
14
  <SEOProvider>
15
15
  <QueryClientProvider client={queryClient}>
@@ -18,5 +18,5 @@ createRoot(document.getElementById('root')!).render(
18
18
  <ReactQueryDevtools initialIsOpen={false} />
19
19
  </QueryClientProvider>
20
20
  </SEOProvider>
21
- </StrictMode>
21
+ </StrictMode>,
22
22
  );
@@ -1,4 +1,4 @@
1
- import { SEO } from '../components/SEO';
1
+ import { SEO } from "../components/SEO";
2
2
 
3
3
  export default function About() {
4
4
  return (
@@ -7,7 +7,11 @@ export default function About() {
7
7
 
8
8
  <div className="flex min-h-screen items-center justify-center bg-black">
9
9
  <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-black sm:items-start">
10
- <div className="text-2xl font-bold text-white">Stackkit</div>
10
+ <div className="flex items-center gap-4 mb-8">
11
+ <div className="text-2xl font-bold text-white">Stackkit</div>
12
+ <span className="text-xl text-zinc-400">+</span>
13
+ <img src="https://react.dev/favicon.ico" alt="React logo" width={32} height={32} />
14
+ </div>
11
15
 
12
16
  <div className="flex flex-col gap-12 sm:text-left">
13
17
  <div>
@@ -1,4 +1,4 @@
1
- import { SEO } from '../components/SEO';
1
+ import { SEO } from "../components/SEO";
2
2
 
3
3
  export default function Home() {
4
4
  return (
@@ -6,7 +6,11 @@ export default function Home() {
6
6
  <SEO title="Home" />
7
7
  <div className="flex min-h-screen items-center justify-center bg-black">
8
8
  <main className="flex min-h-screen w-full max-w-3xl flex-col items-center justify-between py-32 px-16 bg-black sm:items-start">
9
- <div className="text-2xl font-bold text-white">Stackkit</div>
9
+ <div className="flex items-center gap-4 mb-8">
10
+ <div className="text-2xl font-bold text-white">Stackkit</div>
11
+ <span className="text-xl text-zinc-400">+</span>
12
+ <img src="https://react.dev/favicon.ico" alt="React logo" width={32} height={32} />
13
+ </div>
10
14
 
11
15
  <div className="flex flex-col items-center gap-6 text-center sm:items-start sm:text-left">
12
16
  <h1 className="max-w-xs text-3xl font-semibold leading-10 tracking-tight text-zinc-50">
@@ -14,10 +18,10 @@ export default function Home() {
14
18
  </h1>
15
19
  <p className="max-w-md text-lg leading-8 text-zinc-400">
16
20
  This template includes React Router, TanStack Query, Axios, and Tailwind CSS. Check
17
- out the{' '}
21
+ out the{" "}
18
22
  <a href="/about" className="font-medium text-zinc-50 hover:underline">
19
23
  About
20
- </a>{' '}
24
+ </a>{" "}
21
25
  page to learn more about the included features.
22
26
  </p>
23
27
  </div>
@@ -1,5 +1,5 @@
1
- import { Link } from 'react-router';
2
- import { SEO } from '../components/SEO';
1
+ import { Link } from "react-router";
2
+ import { SEO } from "../components/SEO";
3
3
 
4
4
  export default function NotFound() {
5
5
  return (
@@ -1,6 +1,6 @@
1
- import { useQuery } from '@tanstack/react-query';
2
- import { useLoaderData, useParams } from 'react-router';
3
- import { userService } from '../api/services/user.service';
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { useLoaderData, useParams } from "react-router";
3
+ import { userService } from "../api/services/user.service";
4
4
 
5
5
  type User = { id?: string; name?: string; email?: string; avatar?: string; [key: string]: any };
6
6
 
@@ -9,9 +9,9 @@ export default function UserProfile() {
9
9
  const { userId } = useParams();
10
10
 
11
11
  const { data: user = loaderUser ?? {} } = useQuery({
12
- queryKey: ['user', userId],
12
+ queryKey: ["user", userId],
13
13
  queryFn: async () => {
14
- if (!userId) throw new Error('Missing user id');
14
+ if (!userId) throw new Error("Missing user id");
15
15
  return await userService.getUser(userId);
16
16
  },
17
17
  initialData: loaderUser,
@@ -26,7 +26,7 @@ export default function UserProfile() {
26
26
  <img src={user.avatar} alt={user.name} className="w-16 h-16 rounded-full" />
27
27
  ) : (
28
28
  <div className="w-16 h-16 rounded-full bg-zinc-700 flex items-center justify-center text-xl">
29
- {user.name?.[0] ?? 'U'}
29
+ {user.name?.[0] ?? "U"}
30
30
  </div>
31
31
  )}
32
32
  <div>
@@ -1,31 +1,31 @@
1
- import { createBrowserRouter } from 'react-router';
2
- import { userService } from './api/services/user.service';
3
- import { ErrorBoundary } from './components/ErrorBoundary';
4
- import Layout from './components/Layout';
5
- import About from './pages/About';
6
- import Home from './pages/Home';
7
- import NotFound from './pages/NotFound';
8
- import UserProfile from './pages/UserProfile';
1
+ import { createBrowserRouter } from "react-router";
2
+ import { userService } from "./api/services/user.service";
3
+ import { ErrorBoundary } from "./components/ErrorBoundary";
4
+ import Layout from "./components/Layout";
5
+ import About from "./pages/About";
6
+ import Home from "./pages/Home";
7
+ import NotFound from "./pages/NotFound";
8
+ import UserProfile from "./pages/UserProfile";
9
9
 
10
10
  export const router = createBrowserRouter([
11
11
  {
12
- path: '/',
12
+ path: "/",
13
13
  Component: Layout,
14
14
  errorElement: <ErrorBoundary />,
15
15
  children: [
16
16
  { index: true, Component: Home },
17
- { path: 'about', Component: About },
17
+ { path: "about", Component: About },
18
18
  {
19
- path: 'users/:userId',
19
+ path: "users/:userId",
20
20
  loader: async ({ params }) => {
21
21
  const id = params.userId;
22
- if (!id) throw new Response('Missing user id', { status: 400 });
22
+ if (!id) throw new Response("Missing user id", { status: 400 });
23
23
  const user = await userService.getUser(id);
24
24
  return user;
25
25
  },
26
26
  Component: UserProfile,
27
27
  },
28
- { path: '*', Component: NotFound },
28
+ { path: "*", Component: NotFound },
29
29
  ],
30
30
  },
31
31
  ]);
@@ -1,20 +1,20 @@
1
- export interface ApiResponse<T = unknown> {
1
+ type ApiResponse<T = unknown> = {
2
2
  data: T;
3
3
  message?: string;
4
4
  status: number;
5
- }
5
+ };
6
6
 
7
- export interface PaginatedResponse<T> {
7
+ type PaginatedResponse<T> = {
8
8
  data: T[];
9
9
  total: number;
10
10
  page: number;
11
11
  pageSize: number;
12
12
  totalPages: number;
13
- }
13
+ };
14
14
 
15
- export interface ApiError {
15
+ type ApiError = {
16
16
  message: string;
17
17
  code?: string;
18
18
  status?: number;
19
19
  errors?: Record<string, string[]>;
20
- }
20
+ };
@@ -0,0 +1,6 @@
1
+ type User = {
2
+ id: string;
3
+ name: string;
4
+ email: string;
5
+ avatar?: string;
6
+ };
@@ -1,19 +1,19 @@
1
1
  export function cn(...classes: (string | boolean | undefined | null)[]): string {
2
- return classes.filter(Boolean).join(' ');
2
+ return classes.filter(Boolean).join(" ");
3
3
  }
4
4
 
5
5
  export function formatDate(date: Date | string): string {
6
- const d = typeof date === 'string' ? new Date(date) : date;
7
- return d.toLocaleDateString('en-US', {
8
- year: 'numeric',
9
- month: 'long',
10
- day: 'numeric',
6
+ const d = typeof date === "string" ? new Date(date) : date;
7
+ return d.toLocaleDateString("en-US", {
8
+ year: "numeric",
9
+ month: "long",
10
+ day: "numeric",
11
11
  });
12
12
  }
13
13
 
14
14
  export function truncate(str: string, maxLength: number): string {
15
15
  if (str.length <= maxLength) return str;
16
- return str.slice(0, maxLength) + '...';
16
+ return str.slice(0, maxLength) + "...";
17
17
  }
18
18
 
19
19
  export function delay(ms: number): Promise<void> {
@@ -22,7 +22,7 @@ export function delay(ms: number): Promise<void> {
22
22
 
23
23
  export function debounce<T extends (...args: never[]) => unknown>(
24
24
  func: T,
25
- wait: number
25
+ wait: number,
26
26
  ): (...args: Parameters<T>) => void {
27
27
  let timeout: ReturnType<typeof setTimeout> | null = null;
28
28
 
@@ -45,7 +45,7 @@ export function slugify(str: string): string {
45
45
  return str
46
46
  .toLowerCase()
47
47
  .trim()
48
- .replace(/[^\w\s-]/g, '')
49
- .replace(/[\s_-]+/g, '-')
50
- .replace(/^-+|-+$/g, '');
48
+ .replace(/[^\w\s-]/g, "")
49
+ .replace(/[\s_-]+/g, "-")
50
+ .replace(/^-+|-+$/g, "");
51
51
  }