kaneo (minimalist kanban) fork to experiment adding a tangled integration github.com/usekaneo/kaneo
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

style: :sparkles: formatting whole project with biome

Andrej 5bb75f26 e80cf100

+1172 -1171
+6 -6
apps/api/drizzle.config.ts
··· 1 1 import { type Config, defineConfig } from "drizzle-kit"; 2 2 3 3 export default defineConfig({ 4 - out: "./drizzle", 5 - schema: "./src/database/schema.ts", 6 - dialect: "sqlite", 7 - dbCredentials: { 8 - url: "file:local.db", 9 - }, 4 + out: "./drizzle", 5 + schema: "./src/database/schema.ts", 6 + dialect: "sqlite", 7 + dbCredentials: { 8 + url: "file:local.db", 9 + }, 10 10 }) satisfies Config;
+27 -27
apps/api/package.json
··· 1 1 { 2 - "name": "@kaneo/api", 3 - "version": "1.0.50", 4 - "main": "./src/index.ts", 5 - "scripts": { 6 - "test": "echo \"Error: no test specified\" && exit 1", 7 - "dev": "bun run --watch src/index.ts" 8 - }, 9 - "dependencies": { 10 - "@elysiajs/cors": "^1.2.0", 11 - "@elysiajs/jwt": "^1.2.0", 12 - "@elysiajs/websocket": "^0.2.8", 13 - "@paralleldrive/cuid2": "^2.2.2", 14 - "better-sqlite3": "^11.7.0", 15 - "drizzle-kit": "^0.30.1", 16 - "drizzle-orm": "^0.38.3", 17 - "drizzle-typebox": "^0.2.1", 18 - "elysia": "latest", 19 - "@kaneo/typescript-config": "workspace:*" 20 - }, 21 - "devDependencies": { 22 - "bun-types": "latest" 23 - }, 24 - "override": { 25 - "@sinclair/typebox": "0.32.4" 26 - }, 27 - "module": "src/index.ts", 28 - "type": "module" 2 + "name": "@kaneo/api", 3 + "version": "1.0.50", 4 + "main": "./src/index.ts", 5 + "scripts": { 6 + "test": "echo \"Error: no test specified\" && exit 1", 7 + "dev": "bun run --watch src/index.ts" 8 + }, 9 + "dependencies": { 10 + "@elysiajs/cors": "^1.2.0", 11 + "@elysiajs/jwt": "^1.2.0", 12 + "@elysiajs/websocket": "^0.2.8", 13 + "@paralleldrive/cuid2": "^2.2.2", 14 + "better-sqlite3": "^11.7.0", 15 + "drizzle-kit": "^0.30.1", 16 + "drizzle-orm": "^0.38.3", 17 + "drizzle-typebox": "^0.2.1", 18 + "elysia": "latest", 19 + "@kaneo/typescript-config": "workspace:*" 20 + }, 21 + "devDependencies": { 22 + "bun-types": "latest" 23 + }, 24 + "override": { 25 + "@sinclair/typebox": "0.32.4" 26 + }, 27 + "module": "src/index.ts", 28 + "type": "module" 29 29 }
+9 -9
apps/api/src/database/schema.ts
··· 2 2 import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 3 4 4 export const userTable = sqliteTable("user", { 5 - id: text("id") 6 - .$defaultFn(() => createId()) 7 - .primaryKey(), 8 - name: text("name").notNull().unique(), 9 - password: text("password").notNull(), 10 - email: text("email").notNull().unique(), 11 - createdAt: integer("created_at", { mode: "timestamp" }) 12 - .default(new Date()) 13 - .notNull(), 5 + id: text("id") 6 + .$defaultFn(() => createId()) 7 + .primaryKey(), 8 + name: text("name").notNull().unique(), 9 + password: text("password").notNull(), 10 + email: text("email").notNull().unique(), 11 + createdAt: integer("created_at", { mode: "timestamp" }) 12 + .default(new Date()) 13 + .notNull(), 14 14 });
+33 -33
apps/api/src/index.ts
··· 5 5 import createToken from "./user/utils/create-token"; 6 6 7 7 const app = new Elysia() 8 - .use(cors()) 9 - .use(user) 10 - .guard({ 11 - async beforeHandle({ 12 - set, 13 - accessJwt, 14 - refreshJwt, 15 - cookie: { accessToken, refreshToken }, 16 - }) { 17 - const decodedAccessToken = await accessJwt.verify(accessToken.value); 18 - const decodedRefreshToken = await refreshJwt.verify(refreshToken.value); 8 + .use(cors()) 9 + .use(user) 10 + .guard({ 11 + async beforeHandle({ 12 + set, 13 + accessJwt, 14 + refreshJwt, 15 + cookie: { accessToken, refreshToken }, 16 + }) { 17 + const decodedAccessToken = await accessJwt.verify(accessToken.value); 18 + const decodedRefreshToken = await refreshJwt.verify(refreshToken.value); 19 19 20 - if (!decodedAccessToken) { 21 - set.status = "Unauthorized"; 20 + if (!decodedAccessToken) { 21 + set.status = "Unauthorized"; 22 22 23 - return set.status; 24 - } 23 + return set.status; 24 + } 25 25 26 - if (!decodedRefreshToken) { 27 - const refreshToken = await createToken({ 28 - jwt: refreshJwt, 29 - expires: REFRESH_TOKEN_EXPIRY, 30 - payload: { 31 - id: String(decodedAccessToken.id), 32 - }, 33 - }); 26 + if (!decodedRefreshToken) { 27 + const refreshToken = await createToken({ 28 + jwt: refreshJwt, 29 + expires: REFRESH_TOKEN_EXPIRY, 30 + payload: { 31 + id: String(decodedAccessToken.id), 32 + }, 33 + }); 34 34 35 - if (set.cookie) set.cookie.refreshToken = refreshToken; 36 - } 37 - }, 38 - }) 39 - .get("/me", async ({ refreshJwt, cookie: { refreshToken } }) => { 40 - const profile = await refreshJwt.verify(refreshToken.value); 35 + if (set.cookie) set.cookie.refreshToken = refreshToken; 36 + } 37 + }, 38 + }) 39 + .get("/me", async ({ refreshJwt, cookie: { refreshToken } }) => { 40 + const profile = await refreshJwt.verify(refreshToken.value); 41 41 42 - return profile; 43 - }) 44 - .listen(1337); 42 + return profile; 43 + }) 44 + .listen(1337); 45 45 46 46 export type App = typeof app; 47 47 48 48 console.log( 49 - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 49 + `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`, 50 50 );
+95 -95
apps/api/src/tsconfig.json
··· 1 1 { 2 - "compilerOptions": { 3 - /* Visit https://aka.ms/tsconfig to read more about this file */ 2 + "compilerOptions": { 3 + /* Visit https://aka.ms/tsconfig to read more about this file */ 4 4 5 - /* Projects */ 6 - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 5 + /* Projects */ 6 + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 12 13 - /* Language and Environment */ 14 - "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 - // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 - // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 13 + /* Language and Environment */ 14 + "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 + // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 26 27 - /* Modules */ 28 - "module": "ES2022" /* Specify what module code is generated. */, 29 - // "rootDir": "./", /* Specify the root folder within your source files. */ 30 - "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 - "types": [ 36 - "bun-types" 37 - ] /* Specify type package names to be included without being referenced in a source file. */, 38 - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 - // "resolveJsonModule": true, /* Enable importing .json files. */ 41 - // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ 27 + /* Modules */ 28 + "module": "ES2022" /* Specify what module code is generated. */, 29 + // "rootDir": "./", /* Specify the root folder within your source files. */ 30 + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 + "types": [ 36 + "bun-types" 37 + ] /* Specify type package names to be included without being referenced in a source file. */, 38 + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 + // "resolveJsonModule": true, /* Enable importing .json files. */ 41 + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ 42 42 43 - /* JavaScript Support */ 44 - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 45 - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 46 - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 43 + /* JavaScript Support */ 44 + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 45 + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 46 + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 47 47 48 - /* Emit */ 49 - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 50 - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 51 - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 52 - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 53 - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 54 - // "outDir": "./", /* Specify an output folder for all emitted files. */ 55 - // "removeComments": true, /* Disable emitting comments. */ 56 - // "noEmit": true, /* Disable emitting files from a compilation. */ 57 - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 58 - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 59 - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 60 - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 61 - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 63 - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 64 - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 65 - // "newLine": "crlf", /* Set the newline character for emitting files. */ 66 - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 67 - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 68 - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 69 - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 70 - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 71 - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 48 + /* Emit */ 49 + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 50 + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 51 + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 52 + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 53 + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 54 + // "outDir": "./", /* Specify an output folder for all emitted files. */ 55 + // "removeComments": true, /* Disable emitting comments. */ 56 + // "noEmit": true, /* Disable emitting files from a compilation. */ 57 + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 58 + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 59 + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 60 + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 61 + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 63 + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 64 + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 65 + // "newLine": "crlf", /* Set the newline character for emitting files. */ 66 + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 67 + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 68 + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 69 + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 70 + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 71 + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 72 72 73 - /* Interop Constraints */ 74 - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 - "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 77 - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 - "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 73 + /* Interop Constraints */ 74 + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 75 + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 76 + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 77 + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 78 + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 79 79 80 - /* Type Checking */ 81 - "strict": true /* Enable all strict type-checking options. */, 82 - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 83 - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 84 - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 86 - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 88 - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 89 - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 91 - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 92 - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 94 - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 96 - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 97 - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 98 - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 80 + /* Type Checking */ 81 + "strict": true /* Enable all strict type-checking options. */, 82 + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 83 + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 84 + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 85 + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 86 + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 87 + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 88 + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 89 + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 90 + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 91 + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 92 + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 93 + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 94 + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 95 + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 96 + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 97 + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 98 + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 99 + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 100 100 101 - /* Completeness */ 102 - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 - "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 - } 101 + /* Completeness */ 102 + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 103 + "skipLibCheck": true /* Skip type checking all .d.ts files. */ 104 + } 105 105 }
+2 -2
apps/api/src/user/constants.ts
··· 1 1 export const ACCESS_TOKEN_EXPIRY = new Date( 2 - Date.now() + 30 * 24 * 60 * 60 * 1000 // 30d 2 + Date.now() + 30 * 24 * 60 * 60 * 1000, // 30d 3 3 ); 4 4 5 5 export const REFRESH_TOKEN_EXPIRY = new Date( 6 - new Date(Date.now() + 15 * 60 * 1000) // 15m 6 + new Date(Date.now() + 15 * 60 * 1000), // 15m 7 7 );
+17 -17
apps/api/src/user/controllers/sign-in.ts
··· 6 6 type SignInArgs = Static<typeof signInUserSchema>; 7 7 8 8 async function signIn({ 9 - email, 10 - password, 9 + email, 10 + password, 11 11 }: Pick<SignInArgs, "email" | "password">) { 12 - const user = await db.query.userTable.findFirst({ 13 - where: (users, { eq }) => eq(users.email, email), 14 - }); 12 + const user = await db.query.userTable.findFirst({ 13 + where: (users, { eq }) => eq(users.email, email), 14 + }); 15 15 16 - if (!user) { 17 - throw new Error(UserErrors.NotFound); 18 - } 16 + if (!user) { 17 + throw new Error(UserErrors.NotFound); 18 + } 19 19 20 - const isPasswordValid = await Bun.password.verify( 21 - password, 22 - user.password, 23 - "bcrypt" 24 - ); 20 + const isPasswordValid = await Bun.password.verify( 21 + password, 22 + user.password, 23 + "bcrypt", 24 + ); 25 25 26 - if (!isPasswordValid) { 27 - throw new Error(UserErrors.InvalidCredentials); 28 - } 26 + if (!isPasswordValid) { 27 + throw new Error(UserErrors.InvalidCredentials); 28 + } 29 29 30 - return user; 30 + return user; 31 31 } 32 32 33 33 export default signIn;
+15 -15
apps/api/src/user/controllers/sign-up.ts
··· 7 7 type SignUpArgs = Static<typeof signUpUserSchema>; 8 8 9 9 async function signUp({ email, name, password }: SignUpArgs) { 10 - const isEmailTaken = Boolean( 11 - await db.query.userTable.findFirst({ 12 - where: (users, { eq }) => eq(users.email, email), 13 - }) 14 - ); 10 + const isEmailTaken = Boolean( 11 + await db.query.userTable.findFirst({ 12 + where: (users, { eq }) => eq(users.email, email), 13 + }), 14 + ); 15 15 16 - if (isEmailTaken) { 17 - throw new Error(UserErrors.EmailTaken); 18 - } 16 + if (isEmailTaken) { 17 + throw new Error(UserErrors.EmailTaken); 18 + } 19 19 20 - const hashedPassword = await Bun.password.hash(password, { 21 - algorithm: "bcrypt", 22 - }); 20 + const hashedPassword = await Bun.password.hash(password, { 21 + algorithm: "bcrypt", 22 + }); 23 23 24 - const user = await db 25 - .insert(userTable) 26 - .values({ email, name, password: hashedPassword }); 24 + const user = await db 25 + .insert(userTable) 26 + .values({ email, name, password: hashedPassword }); 27 27 28 - return user; 28 + return user; 29 29 } 30 30 31 31 export default signUp;
+5 -5
apps/api/src/user/db/queries.ts
··· 3 3 import { userTable } from "../../database/schema"; 4 4 5 5 export const signUpUserSchema = createInsertSchema(userTable, { 6 - email: t.String({ format: "email" }), 7 - password: t.String(), 8 - name: t.String(), 6 + email: t.String({ format: "email" }), 7 + password: t.String(), 8 + name: t.String(), 9 9 }); 10 10 11 11 export const signInUserSchema = createSelectSchema(userTable, { 12 - email: t.String({ format: "email" }), 13 - password: t.String(), 12 + email: t.String({ format: "email" }), 13 + password: t.String(), 14 14 });
+3 -3
apps/api/src/user/errors.ts
··· 1 1 export enum UserErrors { 2 - NotFound = "UserError: NotFound", 3 - EmailTaken = "UserError: EmailTaken", 4 - InvalidCredentials = "UserError: InvalidCredentials", 2 + NotFound = "UserError: NotFound", 3 + EmailTaken = "UserError: EmailTaken", 4 + InvalidCredentials = "UserError: InvalidCredentials", 5 5 }
+51 -51
apps/api/src/user/index.ts
··· 7 7 import createToken from "./utils/create-token"; 8 8 9 9 const user = new Elysia({ prefix: "/user" }) 10 - .use( 11 - jwt({ 12 - name: "accessJwt", 13 - secret: process.env.JWT_ACCESS ?? "", 14 - }) 15 - ) 16 - .use( 17 - jwt({ 18 - name: "refreshJwt", 19 - secret: process.env.JWT_REFRESH ?? "", 20 - }) 21 - ) 22 - .post( 23 - "/sign-in", 24 - async ({ body, accessJwt, refreshJwt, set }) => { 25 - const user = await signIn(body); 10 + .use( 11 + jwt({ 12 + name: "accessJwt", 13 + secret: process.env.JWT_ACCESS ?? "", 14 + }), 15 + ) 16 + .use( 17 + jwt({ 18 + name: "refreshJwt", 19 + secret: process.env.JWT_REFRESH ?? "", 20 + }), 21 + ) 22 + .post( 23 + "/sign-in", 24 + async ({ body, accessJwt, refreshJwt, set }) => { 25 + const user = await signIn(body); 26 26 27 - const accessToken = await createToken({ 28 - expires: ACCESS_TOKEN_EXPIRY, 29 - jwt: accessJwt, 30 - payload: { 31 - id: user.id, 32 - }, 33 - }); 27 + const accessToken = await createToken({ 28 + expires: ACCESS_TOKEN_EXPIRY, 29 + jwt: accessJwt, 30 + payload: { 31 + id: user.id, 32 + }, 33 + }); 34 34 35 - const refreshToken = await createToken({ 36 - expires: REFRESH_TOKEN_EXPIRY, 37 - jwt: refreshJwt, 38 - payload: { 39 - id: user.id, 40 - }, 41 - }); 35 + const refreshToken = await createToken({ 36 + expires: REFRESH_TOKEN_EXPIRY, 37 + jwt: refreshJwt, 38 + payload: { 39 + id: user.id, 40 + }, 41 + }); 42 42 43 - set.cookie = { 44 - accessToken, 45 - refreshToken, 46 - }; 43 + set.cookie = { 44 + accessToken, 45 + refreshToken, 46 + }; 47 47 48 - return { 49 - user, 50 - }; 51 - }, 52 - { 53 - body: t.Omit(signInUserSchema, ["id", "name", "createdAt"]), 54 - } 55 - ) 56 - .post( 57 - "/sign-up", 58 - async ({ body }) => { 59 - return signUp(body); 60 - }, 61 - { 62 - body: signUpUserSchema, 63 - } 64 - ); 48 + return { 49 + user, 50 + }; 51 + }, 52 + { 53 + body: t.Omit(signInUserSchema, ["id", "name", "createdAt"]), 54 + }, 55 + ) 56 + .post( 57 + "/sign-up", 58 + async ({ body }) => { 59 + return signUp(body); 60 + }, 61 + { 62 + body: signUpUserSchema, 63 + }, 64 + ); 65 65 66 66 export default user;
+12 -12
apps/api/src/user/utils/create-token.ts
··· 3 3 import type { signInUserSchema } from "../db/queries"; 4 4 5 5 const createToken = async ({ 6 - jwt, 7 - payload, 8 - expires, 6 + jwt, 7 + payload, 8 + expires, 9 9 }: { 10 - expires: Date; 11 - jwt: ReturnType<typeof jwtInstance>; 12 - payload: Pick<Static<typeof signInUserSchema>, "id">; 10 + expires: Date; 11 + jwt: ReturnType<typeof jwtInstance>; 12 + payload: Pick<Static<typeof signInUserSchema>, "id">; 13 13 }) => { 14 - return { 15 - value: await jwt.sign(payload), 16 - httpOnly: true, 17 - path: "/", 18 - expires, 19 - }; 14 + return { 15 + value: await jwt.sign(payload), 16 + httpOnly: true, 17 + path: "/", 18 + expires, 19 + }; 20 20 }; 21 21 22 22 export default createToken;
+19 -19
apps/web/components.json
··· 1 1 { 2 - "$schema": "https://ui.shadcn.com/schema.json", 3 - "style": "new-york", 4 - "rsc": false, 5 - "tsx": true, 6 - "tailwind": { 7 - "config": "tailwind.config.js", 8 - "css": "src/index.css", 9 - "baseColor": "zinc", 10 - "cssVariables": true, 11 - "prefix": "" 12 - }, 13 - "aliases": { 14 - "components": "@/components", 15 - "utils": "@/lib/utils", 16 - "ui": "@/components/ui", 17 - "lib": "@/lib", 18 - "hooks": "@/hooks" 19 - }, 20 - "iconLibrary": "lucide" 2 + "$schema": "https://ui.shadcn.com/schema.json", 3 + "style": "new-york", 4 + "rsc": false, 5 + "tsx": true, 6 + "tailwind": { 7 + "config": "tailwind.config.js", 8 + "css": "src/index.css", 9 + "baseColor": "zinc", 10 + "cssVariables": true, 11 + "prefix": "" 12 + }, 13 + "aliases": { 14 + "components": "@/components", 15 + "utils": "@/lib/utils", 16 + "ui": "@/components/ui", 17 + "lib": "@/lib", 18 + "hooks": "@/hooks" 19 + }, 20 + "iconLibrary": "lucide" 21 21 }
+14 -11
apps/web/index.html
··· 1 1 <!doctype html> 2 2 <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 6 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 7 - <title>Vite + React + TS</title> 8 - </head> 9 - <body> 10 - <div id="root"></div> 11 - <script type="module" src="/src/main.tsx"></script> 12 - </body> 13 - </html> 3 + 4 + <head> 5 + <meta charset="UTF-8" /> 6 + <link rel="icon" type="image/svg+xml" href="/vite.svg" /> 7 + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 8 + <title>Vite + React + TS</title> 9 + </head> 10 + 11 + <body> 12 + <div id="root"></div> 13 + <script type="module" src="/src/main.tsx"></script> 14 + </body> 15 + 16 + </html>
+41 -41
apps/web/package.json
··· 1 1 { 2 - "name": "@kaneo/web", 3 - "private": true, 4 - "version": "0.0.0", 5 - "type": "module", 6 - "scripts": { 7 - "dev": "vite", 8 - "build": "tsc -b && vite build", 9 - "preview": "vite preview" 10 - }, 11 - "dependencies": { 12 - "@hookform/resolvers": "^3.9.1", 13 - "@radix-ui/react-avatar": "^1.1.2", 14 - "@radix-ui/react-label": "^2.1.1", 15 - "@radix-ui/react-slot": "^1.1.1", 16 - "@tanstack/react-query": "^5.62.11", 17 - "@tanstack/react-query-devtools": "^5.62.12", 18 - "@tanstack/react-router": "^1.94.1", 19 - "class-variance-authority": "^0.7.1", 20 - "clsx": "^2.1.1", 21 - "framer-motion": "^11.15.0", 22 - "lucide-react": "^0.469.0", 23 - "react": "^18.3.1", 24 - "react-dom": "^18.3.1", 25 - "react-hook-form": "^7.54.2", 26 - "tailwind-merge": "^2.6.0", 27 - "tailwindcss-animate": "^1.0.7", 28 - "zod": "link:@hookform/resolvers/zod" 29 - }, 30 - "devDependencies": { 31 - "@kaneo/libs": "workspace:*", 32 - "@types/node": "^22.10.5", 33 - "@types/react": "^18.3.18", 34 - "@types/react-dom": "^18.3.5", 35 - "@vitejs/plugin-react-swc": "^3.5.0", 36 - "autoprefixer": "^10.4.20", 37 - "globals": "^15.14.0", 38 - "postcss": "^8.4.49", 39 - "tailwindcss": "^3.4.17", 40 - "typescript": "~5.6.2", 41 - "vite": "^6.0.5" 42 - } 2 + "name": "@kaneo/web", 3 + "private": true, 4 + "version": "0.0.0", 5 + "type": "module", 6 + "scripts": { 7 + "dev": "vite", 8 + "build": "tsc -b && vite build", 9 + "preview": "vite preview" 10 + }, 11 + "dependencies": { 12 + "@hookform/resolvers": "^3.9.1", 13 + "@radix-ui/react-avatar": "^1.1.2", 14 + "@radix-ui/react-label": "^2.1.1", 15 + "@radix-ui/react-slot": "^1.1.1", 16 + "@tanstack/react-query": "^5.62.11", 17 + "@tanstack/react-query-devtools": "^5.62.12", 18 + "@tanstack/react-router": "^1.94.1", 19 + "class-variance-authority": "^0.7.1", 20 + "clsx": "^2.1.1", 21 + "framer-motion": "^11.15.0", 22 + "lucide-react": "^0.469.0", 23 + "react": "^18.3.1", 24 + "react-dom": "^18.3.1", 25 + "react-hook-form": "^7.54.2", 26 + "tailwind-merge": "^2.6.0", 27 + "tailwindcss-animate": "^1.0.7", 28 + "zod": "link:@hookform/resolvers/zod" 29 + }, 30 + "devDependencies": { 31 + "@kaneo/libs": "workspace:*", 32 + "@types/node": "^22.10.5", 33 + "@types/react": "^18.3.18", 34 + "@types/react-dom": "^18.3.5", 35 + "@vitejs/plugin-react-swc": "^3.5.0", 36 + "autoprefixer": "^10.4.20", 37 + "globals": "^15.14.0", 38 + "postcss": "^8.4.49", 39 + "tailwindcss": "^3.4.17", 40 + "typescript": "~5.6.2", 41 + "vite": "^6.0.5" 42 + } 43 43 }
+21 -21
apps/web/src/components/auth/layout.tsx
··· 2 2 import { Logo } from "../common/logo"; 3 3 4 4 interface AuthLayoutProps { 5 - children: React.ReactNode; 6 - title: string; 7 - subtitle: string; 5 + children: React.ReactNode; 6 + title: string; 7 + subtitle: string; 8 8 } 9 9 10 10 export function AuthLayout({ children, title, subtitle }: AuthLayoutProps) { 11 - return ( 12 - <div className="min-h-screen bg-gradient-to-b from-zinc-900 to-zinc-950 flex flex-col items-center justify-center p-4"> 13 - <motion.div 14 - initial={{ opacity: 0, y: 20 }} 15 - animate={{ opacity: 1, y: 0 }} 16 - className="w-full max-w-md" 17 - > 18 - <div className="mb-8 text-center"> 19 - <Logo className="mx-auto mb-6" /> 20 - <h1 className="text-3xl font-bold text-zinc-100 mb-2">{title}</h1> 21 - <p className="text-zinc-400">{subtitle}</p> 22 - </div> 11 + return ( 12 + <div className="min-h-screen bg-gradient-to-b from-zinc-900 to-zinc-950 flex flex-col items-center justify-center p-4"> 13 + <motion.div 14 + initial={{ opacity: 0, y: 20 }} 15 + animate={{ opacity: 1, y: 0 }} 16 + className="w-full max-w-md" 17 + > 18 + <div className="mb-8 text-center"> 19 + <Logo className="mx-auto mb-6" /> 20 + <h1 className="text-3xl font-bold text-zinc-100 mb-2">{title}</h1> 21 + <p className="text-zinc-400">{subtitle}</p> 22 + </div> 23 23 24 - <div className="bg-zinc-900/50 backdrop-blur-xl rounded-xl border border-zinc-800/50 p-6"> 25 - {children} 26 - </div> 27 - </motion.div> 28 - </div> 29 - ); 24 + <div className="bg-zinc-900/50 backdrop-blur-xl rounded-xl border border-zinc-800/50 p-6"> 25 + {children} 26 + </div> 27 + </motion.div> 28 + </div> 29 + ); 30 30 }
+111 -111
apps/web/src/components/auth/sign-in-form.tsx
··· 1 1 import { Button } from "@/components/ui/button"; 2 2 import { 3 - Form, 4 - FormControl, 5 - FormField, 6 - FormItem, 7 - FormLabel, 8 - FormMessage, 3 + Form, 4 + FormControl, 5 + FormField, 6 + FormItem, 7 + FormLabel, 8 + FormMessage, 9 9 } from "@/components/ui/form"; 10 10 import { Input } from "@/components/ui/input"; 11 11 import { zodResolver } from "@hookform/resolvers/zod"; ··· 18 18 import { type ZodType, z } from "zod"; 19 19 20 20 type FormValues = { 21 - email: string; 22 - password: string; 21 + email: string; 22 + password: string; 23 23 }; 24 24 25 25 const signInSchema: ZodType<FormValues> = z.object({ 26 - email: z.string().email(), 27 - password: z 28 - .string() 29 - .min(8, { message: "Password is too short" }) 30 - .max(20, { message: "Password is too long" }), 26 + email: z.string().email(), 27 + password: z 28 + .string() 29 + .min(8, { message: "Password is too short" }) 30 + .max(20, { message: "Password is too long" }), 31 31 }); 32 32 33 33 export function SignInForm() { 34 - const [showPassword, setShowPassword] = useState(false); 35 - const { history } = useRouter(); 36 - const form = useForm<FormValues>({ 37 - resolver: zodResolver(signInSchema), 38 - defaultValues: { 39 - email: "", 40 - password: "", 41 - }, 42 - }); 43 - const { mutateAsync } = useMutation({ 44 - mutationFn: () => 45 - api.user["sign-in"].post({ 46 - email: form.getValues().email, 47 - password: form.getValues().password, 48 - }), 49 - }); 34 + const [showPassword, setShowPassword] = useState(false); 35 + const { history } = useRouter(); 36 + const form = useForm<FormValues>({ 37 + resolver: zodResolver(signInSchema), 38 + defaultValues: { 39 + email: "", 40 + password: "", 41 + }, 42 + }); 43 + const { mutateAsync } = useMutation({ 44 + mutationFn: () => 45 + api.user["sign-in"].post({ 46 + email: form.getValues().email, 47 + password: form.getValues().password, 48 + }), 49 + }); 50 50 51 - const onSubmit = async () => { 52 - await mutateAsync(); 53 - history.push("/"); 54 - }; 51 + const onSubmit = async () => { 52 + await mutateAsync(); 53 + history.push("/"); 54 + }; 55 55 56 - return ( 57 - <Form {...form}> 58 - <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> 59 - <div className="space-y-4"> 60 - <div> 61 - <FormField 62 - control={form.control} 63 - name="email" 64 - render={({ field }) => ( 65 - <FormItem> 66 - <FormLabel className="text-sm font-medium text-zinc-300 mb-1.5 block"> 67 - Email 68 - </FormLabel> 69 - <FormControl> 70 - <Input 71 - className="bg-zinc-800/50 border-zinc-700/50 text-zinc-100" 72 - placeholder="you@example.com" 73 - {...field} 74 - /> 75 - </FormControl> 76 - <FormMessage /> 77 - </FormItem> 78 - )} 79 - /> 80 - </div> 56 + return ( 57 + <Form {...form}> 58 + <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4"> 59 + <div className="space-y-4"> 60 + <div> 61 + <FormField 62 + control={form.control} 63 + name="email" 64 + render={({ field }) => ( 65 + <FormItem> 66 + <FormLabel className="text-sm font-medium text-zinc-300 mb-1.5 block"> 67 + Email 68 + </FormLabel> 69 + <FormControl> 70 + <Input 71 + className="bg-zinc-800/50 border-zinc-700/50 text-zinc-100" 72 + placeholder="you@example.com" 73 + {...field} 74 + /> 75 + </FormControl> 76 + <FormMessage /> 77 + </FormItem> 78 + )} 79 + /> 80 + </div> 81 81 82 - <div> 83 - <FormField 84 - control={form.control} 85 - name="password" 86 - render={({ field }) => ( 87 - <FormItem> 88 - <FormLabel className="text-sm font-medium text-zinc-300 mb-1.5 block"> 89 - Password 90 - </FormLabel> 91 - <FormControl> 92 - <div className="relative"> 93 - <Input 94 - className="bg-zinc-800/50 border-zinc-700/50 text-zinc-100 pr-10" 95 - placeholder="••••••••" 96 - type={showPassword ? "text" : "password"} 97 - {...field} 98 - /> 99 - <button 100 - type="button" 101 - onClick={() => setShowPassword(!showPassword)} 102 - className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-300" 103 - > 104 - {showPassword ? ( 105 - <EyeOff size={16} /> 106 - ) : ( 107 - <Eye size={16} /> 108 - )} 109 - </button> 110 - </div> 111 - </FormControl> 112 - <FormMessage /> 113 - </FormItem> 114 - )} 115 - /> 116 - <div className="flex justify-end mt-1"> 117 - <button 118 - type="button" 119 - className="text-sm text-zinc-400 hover:text-zinc-300" 120 - > 121 - Forgot password? 122 - </button> 123 - </div> 124 - </div> 125 - </div> 82 + <div> 83 + <FormField 84 + control={form.control} 85 + name="password" 86 + render={({ field }) => ( 87 + <FormItem> 88 + <FormLabel className="text-sm font-medium text-zinc-300 mb-1.5 block"> 89 + Password 90 + </FormLabel> 91 + <FormControl> 92 + <div className="relative"> 93 + <Input 94 + className="bg-zinc-800/50 border-zinc-700/50 text-zinc-100 pr-10" 95 + placeholder="••••••••" 96 + type={showPassword ? "text" : "password"} 97 + {...field} 98 + /> 99 + <button 100 + type="button" 101 + onClick={() => setShowPassword(!showPassword)} 102 + className="absolute right-3 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-zinc-300" 103 + > 104 + {showPassword ? ( 105 + <EyeOff size={16} /> 106 + ) : ( 107 + <Eye size={16} /> 108 + )} 109 + </button> 110 + </div> 111 + </FormControl> 112 + <FormMessage /> 113 + </FormItem> 114 + )} 115 + /> 116 + <div className="flex justify-end mt-1"> 117 + <button 118 + type="button" 119 + className="text-sm text-zinc-400 hover:text-zinc-300" 120 + > 121 + Forgot password? 122 + </button> 123 + </div> 124 + </div> 125 + </div> 126 126 127 - <Button 128 - type="submit" 129 - className="w-full bg-zinc-100 text-zinc-900 hover:bg-zinc-200 mt-6" 130 - > 131 - Sign In 132 - </Button> 133 - </form> 134 - </Form> 135 - ); 127 + <Button 128 + type="submit" 129 + className="w-full bg-zinc-100 text-zinc-900 hover:bg-zinc-200 mt-6" 130 + > 131 + Sign In 132 + </Button> 133 + </form> 134 + </Form> 135 + ); 136 136 }
+16 -16
apps/web/src/components/auth/toggle.tsx
··· 1 1 import { Link } from "@tanstack/react-router"; 2 2 3 3 interface AuthToggleProps { 4 - message: string; 5 - linkText: string; 6 - linkTo: string; 4 + message: string; 5 + linkText: string; 6 + linkTo: string; 7 7 } 8 8 9 9 export function AuthToggle({ message, linkText, linkTo }: AuthToggleProps) { 10 - return ( 11 - <div className="mt-6 text-center"> 12 - <p className="text-sm text-zinc-400"> 13 - {message}{" "} 14 - <Link 15 - href={linkTo} 16 - className="text-zinc-300 hover:text-zinc-100 font-medium" 17 - > 18 - {linkText} 19 - </Link> 20 - </p> 21 - </div> 22 - ); 10 + return ( 11 + <div className="mt-6 text-center"> 12 + <p className="text-sm text-zinc-400"> 13 + {message}{" "} 14 + <Link 15 + href={linkTo} 16 + className="text-zinc-300 hover:text-zinc-100 font-medium" 17 + > 18 + {linkText} 19 + </Link> 20 + </p> 21 + </div> 22 + ); 23 23 }
+11 -11
apps/web/src/components/common/logo.tsx
··· 1 1 import { LayoutGrid } from "lucide-react"; 2 2 3 3 interface LogoProps { 4 - className?: string; 4 + className?: string; 5 5 } 6 6 7 7 export function Logo({ className = "" }: LogoProps) { 8 - return ( 9 - <div className={`flex items-center gap-2 ${className}`}> 10 - <div className="p-1.5 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-lg shadow-sm"> 11 - <LayoutGrid className="w-5 h-5 text-white" /> 12 - </div> 13 - <span className="text-lg font-semibold bg-gradient-to-br from-indigo-600 to-purple-600 dark:from-indigo-400 dark:to-purple-400 bg-clip-text text-transparent"> 14 - Kaneo 15 - </span> 16 - </div> 17 - ); 8 + return ( 9 + <div className={`flex items-center gap-2 ${className}`}> 10 + <div className="p-1.5 bg-gradient-to-br from-indigo-500 to-purple-500 rounded-lg shadow-sm"> 11 + <LayoutGrid className="w-5 h-5 text-white" /> 12 + </div> 13 + <span className="text-lg font-semibold bg-gradient-to-br from-indigo-600 to-purple-600 dark:from-indigo-400 dark:to-purple-400 bg-clip-text text-transparent"> 14 + Kaneo 15 + </span> 16 + </div> 17 + ); 18 18 }
+13 -13
apps/web/src/components/common/mobile-header.tsx
··· 2 2 import { Logo } from "./logo"; 3 3 4 4 interface MobileHeaderProps { 5 - onMenuClick: () => void; 5 + onMenuClick: () => void; 6 6 } 7 7 8 8 export function MobileHeader({ onMenuClick }: MobileHeaderProps) { 9 - return ( 10 - <div className="flex items-center justify-between p-4 border-b border-zinc-800 md:hidden"> 11 - <Logo /> 12 - <button 13 - type="button" 14 - onClick={onMenuClick} 15 - className="p-2 text-zinc-400 hover:text-zinc-300 hover:bg-zinc-800 rounded-lg transition-colors" 16 - > 17 - <Menu className="h-6 w-6" /> 18 - </button> 19 - </div> 20 - ); 9 + return ( 10 + <div className="flex items-center justify-between p-4 border-b border-zinc-800 md:hidden"> 11 + <Logo /> 12 + <button 13 + type="button" 14 + onClick={onMenuClick} 15 + className="p-2 text-zinc-400 hover:text-zinc-300 hover:bg-zinc-800 rounded-lg transition-colors" 16 + > 17 + <Menu className="h-6 w-6" /> 18 + </button> 19 + </div> 20 + ); 21 21 }
+52 -52
apps/web/src/components/common/sidebar/index.tsx
··· 7 7 import { SidebarHeader } from "./sidebar-header"; 8 8 9 9 export function Sidebar() { 10 - const [isOpen, setIsOpen] = useState(false); 11 - // const { workspaces, currentWorkspace, setCurrentWorkspace } = useKaneoStore(); 12 - const currentUser = { 13 - name: "John Doe", 14 - email: "john@example.com", 15 - avatar: 16 - "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=32&h=32&fit=crop&crop=face", 17 - }; 10 + const [isOpen, setIsOpen] = useState(false); 11 + // const { workspaces, currentWorkspace, setCurrentWorkspace } = useKaneoStore(); 12 + const currentUser = { 13 + name: "John Doe", 14 + email: "john@example.com", 15 + avatar: 16 + "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=32&h=32&fit=crop&crop=face", 17 + }; 18 18 19 - return ( 20 - <> 21 - <MobileHeader onMenuClick={() => setIsOpen(true)} /> 19 + return ( 20 + <> 21 + <MobileHeader onMenuClick={() => setIsOpen(true)} /> 22 22 23 - <div 24 - className={cn( 25 - "fixed inset-0 z-40 bg-white/80 dark:bg-zinc-950/80 backdrop-blur-sm md:hidden", 26 - isOpen ? "opacity-100" : "opacity-0 pointer-events-none" 27 - )} 28 - > 29 - <div 30 - className={cn( 31 - "fixed inset-y-0 left-0 w-64 bg-white dark:bg-zinc-900 transform transition-transform duration-200 ease-in-out", 32 - isOpen ? "translate-x-0" : "-translate-x-full" 33 - )} 34 - > 35 - <div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800"> 36 - <Logo /> 37 - <button 38 - type="button" 39 - onClick={() => setIsOpen(false)} 40 - className="p-2 text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors" 41 - > 42 - <X className="h-5 w-5" /> 43 - </button> 44 - </div> 23 + <div 24 + className={cn( 25 + "fixed inset-0 z-40 bg-white/80 dark:bg-zinc-950/80 backdrop-blur-sm md:hidden", 26 + isOpen ? "opacity-100" : "opacity-0 pointer-events-none", 27 + )} 28 + > 29 + <div 30 + className={cn( 31 + "fixed inset-y-0 left-0 w-64 bg-white dark:bg-zinc-900 transform transition-transform duration-200 ease-in-out", 32 + isOpen ? "translate-x-0" : "-translate-x-full", 33 + )} 34 + > 35 + <div className="flex items-center justify-between p-4 border-b border-zinc-200 dark:border-zinc-800"> 36 + <Logo /> 37 + <button 38 + type="button" 39 + onClick={() => setIsOpen(false)} 40 + className="p-2 text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300 hover:bg-zinc-100 dark:hover:bg-zinc-800 rounded-lg transition-colors" 41 + > 42 + <X className="h-5 w-5" /> 43 + </button> 44 + </div> 45 45 46 - <SidebarContent 47 - workspaces={[]} 48 - currentWorkspace={"Andrej's workspace"} 49 - setCurrentWorkspace={() => {}} 50 - currentUser={currentUser} 51 - /> 52 - </div> 53 - </div> 46 + <SidebarContent 47 + workspaces={[]} 48 + currentWorkspace={"Andrej's workspace"} 49 + setCurrentWorkspace={() => {}} 50 + currentUser={currentUser} 51 + /> 52 + </div> 53 + </div> 54 54 55 - <div className="hidden md:flex w-64 bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 h-screen flex-col"> 56 - <SidebarHeader /> 57 - <SidebarContent 58 - workspaces={[]} 59 - currentWorkspace={"Andrej's workspace"} 60 - setCurrentWorkspace={() => {}} 61 - currentUser={currentUser} 62 - /> 63 - </div> 64 - </> 65 - ); 55 + <div className="hidden md:flex w-64 bg-white dark:bg-zinc-900 border-r border-zinc-200 dark:border-zinc-800 h-screen flex-col"> 56 + <SidebarHeader /> 57 + <SidebarContent 58 + workspaces={[]} 59 + currentWorkspace={"Andrej's workspace"} 60 + setCurrentWorkspace={() => {}} 61 + currentUser={currentUser} 62 + /> 63 + </div> 64 + </> 65 + ); 66 66 }
+100 -100
apps/web/src/components/common/sidebar/sidebar-content.tsx
··· 2 2 import { Avatar, AvatarFallback, AvatarImage } from "../../ui/avatar"; 3 3 4 4 interface SidebarContentProps { 5 - // biome-ignore lint/suspicious/noExplicitAny: <explanation> 6 - workspaces: any[]; 7 - currentWorkspace: string | null; 8 - setCurrentWorkspace: (id: string) => void; 9 - currentUser: { 10 - name: string; 11 - email: string; 12 - avatar: string; 13 - }; 5 + // biome-ignore lint/suspicious/noExplicitAny: <explanation> 6 + workspaces: any[]; 7 + currentWorkspace: string | null; 8 + setCurrentWorkspace: (id: string) => void; 9 + currentUser: { 10 + name: string; 11 + email: string; 12 + avatar: string; 13 + }; 14 14 } 15 15 16 16 export function SidebarContent({ 17 - workspaces, 18 - currentWorkspace, 19 - setCurrentWorkspace, 20 - currentUser, 17 + workspaces, 18 + currentWorkspace, 19 + setCurrentWorkspace, 20 + currentUser, 21 21 }: SidebarContentProps) { 22 - return ( 23 - <> 24 - <nav className="flex-1 overflow-y-auto p-3"> 25 - <div className="space-y-4"> 26 - <div> 27 - <h2 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2 px-3"> 28 - Workspaces 29 - </h2> 30 - <div className="space-y-1"> 31 - {workspaces.map((workspace) => ( 32 - <button 33 - type="button" 34 - key={workspace.id} 35 - onClick={() => setCurrentWorkspace(workspace.id)} 36 - className={`w-full text-left px-3 py-2 rounded-lg flex items-center transition-colors ${ 37 - currentWorkspace === workspace.id 38 - ? "bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-400" 39 - : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800" 40 - }`} 41 - > 42 - <Folder 43 - className={`w-4 h-4 mr-2 ${ 44 - currentWorkspace === workspace.id 45 - ? "text-indigo-600 dark:text-indigo-400" 46 - : "text-zinc-500 dark:text-zinc-400" 47 - }`} 48 - /> 49 - {workspace.name} 50 - </button> 51 - ))} 22 + return ( 23 + <> 24 + <nav className="flex-1 overflow-y-auto p-3"> 25 + <div className="space-y-4"> 26 + <div> 27 + <h2 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2 px-3"> 28 + Workspaces 29 + </h2> 30 + <div className="space-y-1"> 31 + {workspaces.map((workspace) => ( 32 + <button 33 + type="button" 34 + key={workspace.id} 35 + onClick={() => setCurrentWorkspace(workspace.id)} 36 + className={`w-full text-left px-3 py-2 rounded-lg flex items-center transition-colors ${ 37 + currentWorkspace === workspace.id 38 + ? "bg-indigo-50 text-indigo-600 dark:bg-indigo-500/10 dark:text-indigo-400" 39 + : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800" 40 + }`} 41 + > 42 + <Folder 43 + className={`w-4 h-4 mr-2 ${ 44 + currentWorkspace === workspace.id 45 + ? "text-indigo-600 dark:text-indigo-400" 46 + : "text-zinc-500 dark:text-zinc-400" 47 + }`} 48 + /> 49 + {workspace.name} 50 + </button> 51 + ))} 52 52 53 - <button 54 - type="button" 55 - className="w-full text-left px-3 py-2 text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center transition-colors" 56 - > 57 - <Plus className="w-4 h-4 mr-2 text-zinc-500 dark:text-zinc-400" /> 58 - Add Workspace 59 - </button> 60 - </div> 61 - </div> 53 + <button 54 + type="button" 55 + className="w-full text-left px-3 py-2 text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center transition-colors" 56 + > 57 + <Plus className="w-4 h-4 mr-2 text-zinc-500 dark:text-zinc-400" /> 58 + Add Workspace 59 + </button> 60 + </div> 61 + </div> 62 62 63 - <div> 64 - <h2 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2 px-3"> 65 - Team 66 - </h2> 67 - <button 68 - type="button" 69 - className="w-full text-left px-3 py-2 text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center transition-colors" 70 - > 71 - <Users className="w-4 h-4 mr-2 text-zinc-500 dark:text-zinc-400" /> 72 - Manage Team 73 - </button> 74 - </div> 75 - </div> 76 - </nav> 63 + <div> 64 + <h2 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 uppercase tracking-wider mb-2 px-3"> 65 + Team 66 + </h2> 67 + <button 68 + type="button" 69 + className="w-full text-left px-3 py-2 text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center transition-colors" 70 + > 71 + <Users className="w-4 h-4 mr-2 text-zinc-500 dark:text-zinc-400" /> 72 + Manage Team 73 + </button> 74 + </div> 75 + </div> 76 + </nav> 77 77 78 - <div className="p-4 border-t border-zinc-200 dark:border-zinc-800"> 79 - <div className="flex items-center gap-3 mb-3"> 80 - <Avatar> 81 - <AvatarImage src={currentUser.avatar} alt={currentUser.name} /> 82 - <AvatarFallback>{currentUser.name.charAt(0)}</AvatarFallback> 83 - </Avatar> 84 - <div className="flex-1 min-w-0"> 85 - <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate"> 86 - {currentUser.name} 87 - </p> 88 - <p className="text-xs text-zinc-500 dark:text-zinc-400 truncate"> 89 - {currentUser.email} 90 - </p> 91 - </div> 92 - </div> 93 - <div className="flex gap-1"> 94 - <button 95 - type="button" 96 - className="flex-1 px-2 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center justify-center transition-colors" 97 - > 98 - <Settings className="w-3 h-3 mr-1" /> 99 - Settings 100 - </button> 101 - <button 102 - type="button" 103 - className="flex-1 px-2 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center justify-center transition-colors" 104 - > 105 - <LogOut className="w-3 h-3 mr-1" /> 106 - Sign out 107 - </button> 108 - </div> 109 - </div> 110 - </> 111 - ); 78 + <div className="p-4 border-t border-zinc-200 dark:border-zinc-800"> 79 + <div className="flex items-center gap-3 mb-3"> 80 + <Avatar> 81 + <AvatarImage src={currentUser.avatar} alt={currentUser.name} /> 82 + <AvatarFallback>{currentUser.name.charAt(0)}</AvatarFallback> 83 + </Avatar> 84 + <div className="flex-1 min-w-0"> 85 + <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100 truncate"> 86 + {currentUser.name} 87 + </p> 88 + <p className="text-xs text-zinc-500 dark:text-zinc-400 truncate"> 89 + {currentUser.email} 90 + </p> 91 + </div> 92 + </div> 93 + <div className="flex gap-1"> 94 + <button 95 + type="button" 96 + className="flex-1 px-2 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center justify-center transition-colors" 97 + > 98 + <Settings className="w-3 h-3 mr-1" /> 99 + Settings 100 + </button> 101 + <button 102 + type="button" 103 + className="flex-1 px-2 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800 rounded-lg flex items-center justify-center transition-colors" 104 + > 105 + <LogOut className="w-3 h-3 mr-1" /> 106 + Sign out 107 + </button> 108 + </div> 109 + </div> 110 + </> 111 + ); 112 112 }
+5 -5
apps/web/src/components/common/sidebar/sidebar-header.tsx
··· 1 1 import { Logo } from "../logo"; 2 2 3 3 export function SidebarHeader() { 4 - return ( 5 - <div className="p-4 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between"> 6 - <Logo /> 7 - </div> 8 - ); 4 + return ( 5 + <div className="p-4 border-b border-zinc-200 dark:border-zinc-800 flex items-center justify-between"> 6 + <Logo /> 7 + </div> 8 + ); 9 9 }
+40 -40
apps/web/src/components/ui/button.tsx
··· 5 5 import { cn } from "@/lib/utils"; 6 6 7 7 const buttonVariants = cva( 8 - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 - { 10 - variants: { 11 - variant: { 12 - default: 13 - "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 - destructive: 15 - "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 - outline: 17 - "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 - secondary: 19 - "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 - ghost: "hover:bg-accent hover:text-accent-foreground", 21 - link: "text-primary underline-offset-4 hover:underline", 22 - }, 23 - size: { 24 - default: "h-9 px-4 py-2", 25 - sm: "h-8 rounded-md px-3 text-xs", 26 - lg: "h-10 rounded-md px-8", 27 - icon: "h-9 w-9", 28 - }, 29 - }, 30 - defaultVariants: { 31 - variant: "default", 32 - size: "default", 33 - }, 34 - } 8 + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", 9 + { 10 + variants: { 11 + variant: { 12 + default: 13 + "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 + destructive: 15 + "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 + outline: 17 + "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 + secondary: 19 + "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 + ghost: "hover:bg-accent hover:text-accent-foreground", 21 + link: "text-primary underline-offset-4 hover:underline", 22 + }, 23 + size: { 24 + default: "h-9 px-4 py-2", 25 + sm: "h-8 rounded-md px-3 text-xs", 26 + lg: "h-10 rounded-md px-8", 27 + icon: "h-9 w-9", 28 + }, 29 + }, 30 + defaultVariants: { 31 + variant: "default", 32 + size: "default", 33 + }, 34 + }, 35 35 ); 36 36 37 37 export interface ButtonProps 38 - extends React.ButtonHTMLAttributes<HTMLButtonElement>, 39 - VariantProps<typeof buttonVariants> { 40 - asChild?: boolean; 38 + extends React.ButtonHTMLAttributes<HTMLButtonElement>, 39 + VariantProps<typeof buttonVariants> { 40 + asChild?: boolean; 41 41 } 42 42 43 43 const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( 44 - ({ className, variant, size, asChild = false, ...props }, ref) => { 45 - const Comp = asChild ? Slot : "button"; 46 - return ( 47 - <Comp 48 - className={cn(buttonVariants({ variant, size, className }))} 49 - ref={ref} 50 - {...props} 51 - /> 52 - ); 53 - } 44 + ({ className, variant, size, asChild = false, ...props }, ref) => { 45 + const Comp = asChild ? Slot : "button"; 46 + return ( 47 + <Comp 48 + className={cn(buttonVariants({ variant, size, className }))} 49 + ref={ref} 50 + {...props} 51 + /> 52 + ); 53 + }, 54 54 ); 55 55 Button.displayName = "Button"; 56 56
+108 -108
apps/web/src/components/ui/form.tsx
··· 2 2 import { Slot } from "@radix-ui/react-slot"; 3 3 import * as React from "react"; 4 4 import { 5 - Controller, 6 - type ControllerProps, 7 - type FieldPath, 8 - type FieldValues, 9 - FormProvider, 10 - useFormContext, 5 + Controller, 6 + type ControllerProps, 7 + type FieldPath, 8 + type FieldValues, 9 + FormProvider, 10 + useFormContext, 11 11 } from "react-hook-form"; 12 12 13 13 import { Label } from "@/components/ui/label"; ··· 16 16 const Form = FormProvider; 17 17 18 18 type FormFieldContextValue< 19 - TFieldValues extends FieldValues = FieldValues, 20 - TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, 19 + TFieldValues extends FieldValues = FieldValues, 20 + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, 21 21 > = { 22 - name: TName; 22 + name: TName; 23 23 }; 24 24 25 25 const FormFieldContext = React.createContext<FormFieldContextValue>( 26 - {} as FormFieldContextValue 26 + {} as FormFieldContextValue, 27 27 ); 28 28 29 29 const FormField = < 30 - TFieldValues extends FieldValues = FieldValues, 31 - TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, 30 + TFieldValues extends FieldValues = FieldValues, 31 + TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>, 32 32 >({ 33 - ...props 33 + ...props 34 34 }: ControllerProps<TFieldValues, TName>) => { 35 - return ( 36 - <FormFieldContext.Provider value={{ name: props.name }}> 37 - <Controller {...props} /> 38 - </FormFieldContext.Provider> 39 - ); 35 + return ( 36 + <FormFieldContext.Provider value={{ name: props.name }}> 37 + <Controller {...props} /> 38 + </FormFieldContext.Provider> 39 + ); 40 40 }; 41 41 42 42 const useFormField = () => { 43 - const fieldContext = React.useContext(FormFieldContext); 44 - const itemContext = React.useContext(FormItemContext); 45 - const { getFieldState, formState } = useFormContext(); 43 + const fieldContext = React.useContext(FormFieldContext); 44 + const itemContext = React.useContext(FormItemContext); 45 + const { getFieldState, formState } = useFormContext(); 46 46 47 - const fieldState = getFieldState(fieldContext.name, formState); 47 + const fieldState = getFieldState(fieldContext.name, formState); 48 48 49 - if (!fieldContext) { 50 - throw new Error("useFormField should be used within <FormField>"); 51 - } 49 + if (!fieldContext) { 50 + throw new Error("useFormField should be used within <FormField>"); 51 + } 52 52 53 - const { id } = itemContext; 53 + const { id } = itemContext; 54 54 55 - return { 56 - id, 57 - name: fieldContext.name, 58 - formItemId: `${id}-form-item`, 59 - formDescriptionId: `${id}-form-item-description`, 60 - formMessageId: `${id}-form-item-message`, 61 - ...fieldState, 62 - }; 55 + return { 56 + id, 57 + name: fieldContext.name, 58 + formItemId: `${id}-form-item`, 59 + formDescriptionId: `${id}-form-item-description`, 60 + formMessageId: `${id}-form-item-message`, 61 + ...fieldState, 62 + }; 63 63 }; 64 64 65 65 type FormItemContextValue = { 66 - id: string; 66 + id: string; 67 67 }; 68 68 69 69 const FormItemContext = React.createContext<FormItemContextValue>( 70 - {} as FormItemContextValue 70 + {} as FormItemContextValue, 71 71 ); 72 72 73 73 const FormItem = React.forwardRef< 74 - HTMLDivElement, 75 - React.HTMLAttributes<HTMLDivElement> 74 + HTMLDivElement, 75 + React.HTMLAttributes<HTMLDivElement> 76 76 >(({ className, ...props }, ref) => { 77 - const id = React.useId(); 77 + const id = React.useId(); 78 78 79 - return ( 80 - <FormItemContext.Provider value={{ id }}> 81 - <div ref={ref} className={cn("space-y-2", className)} {...props} /> 82 - </FormItemContext.Provider> 83 - ); 79 + return ( 80 + <FormItemContext.Provider value={{ id }}> 81 + <div ref={ref} className={cn("space-y-2", className)} {...props} /> 82 + </FormItemContext.Provider> 83 + ); 84 84 }); 85 85 FormItem.displayName = "FormItem"; 86 86 87 87 const FormLabel = React.forwardRef< 88 - React.ElementRef<typeof LabelPrimitive.Root>, 89 - React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> 88 + React.ElementRef<typeof LabelPrimitive.Root>, 89 + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> 90 90 >(({ className, ...props }, ref) => { 91 - const { error, formItemId } = useFormField(); 91 + const { error, formItemId } = useFormField(); 92 92 93 - return ( 94 - <Label 95 - ref={ref} 96 - className={cn(error && "text-destructive", className)} 97 - htmlFor={formItemId} 98 - {...props} 99 - /> 100 - ); 93 + return ( 94 + <Label 95 + ref={ref} 96 + className={cn(error && "text-destructive", className)} 97 + htmlFor={formItemId} 98 + {...props} 99 + /> 100 + ); 101 101 }); 102 102 FormLabel.displayName = "FormLabel"; 103 103 104 104 const FormControl = React.forwardRef< 105 - React.ElementRef<typeof Slot>, 106 - React.ComponentPropsWithoutRef<typeof Slot> 105 + React.ElementRef<typeof Slot>, 106 + React.ComponentPropsWithoutRef<typeof Slot> 107 107 >(({ ...props }, ref) => { 108 - const { error, formItemId, formDescriptionId, formMessageId } = 109 - useFormField(); 108 + const { error, formItemId, formDescriptionId, formMessageId } = 109 + useFormField(); 110 110 111 - return ( 112 - <Slot 113 - ref={ref} 114 - id={formItemId} 115 - aria-describedby={ 116 - !error 117 - ? `${formDescriptionId}` 118 - : `${formDescriptionId} ${formMessageId}` 119 - } 120 - aria-invalid={!!error} 121 - {...props} 122 - /> 123 - ); 111 + return ( 112 + <Slot 113 + ref={ref} 114 + id={formItemId} 115 + aria-describedby={ 116 + !error 117 + ? `${formDescriptionId}` 118 + : `${formDescriptionId} ${formMessageId}` 119 + } 120 + aria-invalid={!!error} 121 + {...props} 122 + /> 123 + ); 124 124 }); 125 125 FormControl.displayName = "FormControl"; 126 126 127 127 const FormDescription = React.forwardRef< 128 - HTMLParagraphElement, 129 - React.HTMLAttributes<HTMLParagraphElement> 128 + HTMLParagraphElement, 129 + React.HTMLAttributes<HTMLParagraphElement> 130 130 >(({ className, ...props }, ref) => { 131 - const { formDescriptionId } = useFormField(); 131 + const { formDescriptionId } = useFormField(); 132 132 133 - return ( 134 - <p 135 - ref={ref} 136 - id={formDescriptionId} 137 - className={cn("text-[0.8rem] text-muted-foreground", className)} 138 - {...props} 139 - /> 140 - ); 133 + return ( 134 + <p 135 + ref={ref} 136 + id={formDescriptionId} 137 + className={cn("text-[0.8rem] text-muted-foreground", className)} 138 + {...props} 139 + /> 140 + ); 141 141 }); 142 142 FormDescription.displayName = "FormDescription"; 143 143 144 144 const FormMessage = React.forwardRef< 145 - HTMLParagraphElement, 146 - React.HTMLAttributes<HTMLParagraphElement> 145 + HTMLParagraphElement, 146 + React.HTMLAttributes<HTMLParagraphElement> 147 147 >(({ className, children, ...props }, ref) => { 148 - const { error, formMessageId } = useFormField(); 149 - const body = error ? String(error?.message) : children; 148 + const { error, formMessageId } = useFormField(); 149 + const body = error ? String(error?.message) : children; 150 150 151 - if (!body) { 152 - return null; 153 - } 151 + if (!body) { 152 + return null; 153 + } 154 154 155 - return ( 156 - <p 157 - ref={ref} 158 - id={formMessageId} 159 - className={cn("text-[0.8rem] font-medium text-destructive", className)} 160 - {...props} 161 - > 162 - {body} 163 - </p> 164 - ); 155 + return ( 156 + <p 157 + ref={ref} 158 + id={formMessageId} 159 + className={cn("text-[0.8rem] font-medium text-destructive", className)} 160 + {...props} 161 + > 162 + {body} 163 + </p> 164 + ); 165 165 }); 166 166 FormMessage.displayName = "FormMessage"; 167 167 168 168 export { 169 - useFormField, 170 - Form, 171 - FormItem, 172 - FormLabel, 173 - FormControl, 174 - FormDescription, 175 - FormMessage, 176 - FormField, 169 + useFormField, 170 + Form, 171 + FormItem, 172 + FormLabel, 173 + FormControl, 174 + FormDescription, 175 + FormMessage, 176 + FormField, 177 177 };
+13 -13
apps/web/src/components/ui/input.tsx
··· 3 3 import { cn } from "@/lib/utils"; 4 4 5 5 const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>( 6 - ({ className, type, ...props }, ref) => { 7 - return ( 8 - <input 9 - type={type} 10 - className={cn( 11 - "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 12 - className 13 - )} 14 - ref={ref} 15 - {...props} 16 - /> 17 - ); 18 - } 6 + ({ className, type, ...props }, ref) => { 7 + return ( 8 + <input 9 + type={type} 10 + className={cn( 11 + "flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", 12 + className, 13 + )} 14 + ref={ref} 15 + {...props} 16 + /> 17 + ); 18 + }, 19 19 ); 20 20 Input.displayName = "Input"; 21 21
+9 -9
apps/web/src/components/ui/label.tsx
··· 5 5 import { cn } from "@/lib/utils"; 6 6 7 7 const labelVariants = cva( 8 - "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 8 + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", 9 9 ); 10 10 11 11 const Label = React.forwardRef< 12 - React.ElementRef<typeof LabelPrimitive.Root>, 13 - React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & 14 - VariantProps<typeof labelVariants> 12 + React.ElementRef<typeof LabelPrimitive.Root>, 13 + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & 14 + VariantProps<typeof labelVariants> 15 15 >(({ className, ...props }, ref) => ( 16 - <LabelPrimitive.Root 17 - ref={ref} 18 - className={cn(labelVariants(), className)} 19 - {...props} 20 - /> 16 + <LabelPrimitive.Root 17 + ref={ref} 18 + className={cn(labelVariants(), className)} 19 + {...props} 20 + /> 21 21 )); 22 22 Label.displayName = LabelPrimitive.Root.displayName; 23 23
+74 -73
apps/web/src/index.css
··· 7 7 @tailwind utilities; 8 8 9 9 @layer base { 10 - :root { 11 - --background: 0 0% 100%; 12 - --foreground: 240 10% 3.9%; 13 - --card: 0 0% 100%; 14 - --card-foreground: 240 10% 3.9%; 15 - --popover: 0 0% 100%; 16 - --popover-foreground: 240 10% 3.9%; 17 - --primary: 240 5.9% 10%; 18 - --primary-foreground: 0 0% 98%; 19 - --secondary: 240 4.8% 95.9%; 20 - --secondary-foreground: 240 5.9% 10%; 21 - --muted: 240 4.8% 95.9%; 22 - --muted-foreground: 240 3.8% 46.1%; 23 - --accent: 240 4.8% 95.9%; 24 - --accent-foreground: 240 5.9% 10%; 25 - --destructive: 0 84.2% 60.2%; 26 - --destructive-foreground: 0 0% 98%; 27 - --border: 240 5.9% 90%; 28 - --input: 240 5.9% 90%; 29 - --ring: 240 10% 3.9%; 30 - --radius: 0.5rem; 31 - --chart-1: 12 76% 61%; 32 - --chart-2: 173 58% 39%; 33 - --chart-3: 197 37% 24%; 34 - --chart-4: 43 74% 66%; 35 - --chart-5: 27 87% 67%; 36 - } 10 + :root { 11 + --background: 0 0% 100%; 12 + --foreground: 240 10% 3.9%; 13 + --card: 0 0% 100%; 14 + --card-foreground: 240 10% 3.9%; 15 + --popover: 0 0% 100%; 16 + --popover-foreground: 240 10% 3.9%; 17 + --primary: 240 5.9% 10%; 18 + --primary-foreground: 0 0% 98%; 19 + --secondary: 240 4.8% 95.9%; 20 + --secondary-foreground: 240 5.9% 10%; 21 + --muted: 240 4.8% 95.9%; 22 + --muted-foreground: 240 3.8% 46.1%; 23 + --accent: 240 4.8% 95.9%; 24 + --accent-foreground: 240 5.9% 10%; 25 + --destructive: 0 84.2% 60.2%; 26 + --destructive-foreground: 0 0% 98%; 27 + --border: 240 5.9% 90%; 28 + --input: 240 5.9% 90%; 29 + --ring: 240 10% 3.9%; 30 + --radius: 0.5rem; 31 + --chart-1: 12 76% 61%; 32 + --chart-2: 173 58% 39%; 33 + --chart-3: 197 37% 24%; 34 + --chart-4: 43 74% 66%; 35 + --chart-5: 27 87% 67%; 36 + } 37 37 38 - .dark { 39 - --background: 240 10% 3.9%; 40 - --foreground: 0 0% 98%; 41 - --card: 240 10% 3.9%; 42 - --card-foreground: 0 0% 98%; 43 - --popover: 240 10% 3.9%; 44 - --popover-foreground: 0 0% 98%; 45 - --primary: 0 0% 98%; 46 - --primary-foreground: 240 5.9% 10%; 47 - --secondary: 240 3.7% 15.9%; 48 - --secondary-foreground: 0 0% 98%; 49 - --muted: 240 3.7% 15.9%; 50 - --muted-foreground: 240 5% 64.9%; 51 - --accent: 240 3.7% 15.9%; 52 - --accent-foreground: 0 0% 98%; 53 - --destructive: 0 62.8% 30.6%; 54 - --destructive-foreground: 0 0% 98%; 55 - --border: 240 3.7% 15.9%; 56 - --input: 240 3.7% 15.9%; 57 - --ring: 240 4.9% 83.9%; 58 - --chart-1: 220 70% 50%; 59 - --chart-2: 160 60% 45%; 60 - --chart-3: 30 80% 55%; 61 - --chart-4: 280 65% 60%; 62 - --chart-5: 340 75% 55%; 63 - } 38 + .dark { 39 + --background: 240 10% 3.9%; 40 + --foreground: 0 0% 98%; 41 + --card: 240 10% 3.9%; 42 + --card-foreground: 0 0% 98%; 43 + --popover: 240 10% 3.9%; 44 + --popover-foreground: 0 0% 98%; 45 + --primary: 0 0% 98%; 46 + --primary-foreground: 240 5.9% 10%; 47 + --secondary: 240 3.7% 15.9%; 48 + --secondary-foreground: 0 0% 98%; 49 + --muted: 240 3.7% 15.9%; 50 + --muted-foreground: 240 5% 64.9%; 51 + --accent: 240 3.7% 15.9%; 52 + --accent-foreground: 0 0% 98%; 53 + --destructive: 0 62.8% 30.6%; 54 + --destructive-foreground: 0 0% 98%; 55 + --border: 240 3.7% 15.9%; 56 + --input: 240 3.7% 15.9%; 57 + --ring: 240 4.9% 83.9%; 58 + --chart-1: 220 70% 50%; 59 + --chart-2: 160 60% 45%; 60 + --chart-3: 30 80% 55%; 61 + --chart-4: 280 65% 60%; 62 + --chart-5: 340 75% 55%; 63 + } 64 64 } 65 65 66 66 /* Custom scrollbar styles */ 67 67 @layer utilities { 68 - .scrollbar-thin { 69 - scrollbar-width: thin; 70 - } 68 + .scrollbar-thin { 69 + scrollbar-width: thin; 70 + } 71 71 72 - .scrollbar-thumb-zinc-700::-webkit-scrollbar-thumb { 73 - background-color: rgb(63 63 70); 74 - border-radius: 9999px; 75 - } 72 + .scrollbar-thumb-zinc-700::-webkit-scrollbar-thumb { 73 + background-color: rgb(63 63 70); 74 + border-radius: 9999px; 75 + } 76 76 77 - .scrollbar-track-zinc-900::-webkit-scrollbar-track { 78 - background-color: rgb(24 24 27); 79 - } 77 + .scrollbar-track-zinc-900::-webkit-scrollbar-track { 78 + background-color: rgb(24 24 27); 79 + } 80 80 81 - .scrollbar-thin::-webkit-scrollbar { 82 - width: 6px; 83 - height: 6px; 84 - } 81 + .scrollbar-thin::-webkit-scrollbar { 82 + width: 6px; 83 + height: 6px; 84 + } 85 85 } 86 + 86 87 @layer base { 87 - * { 88 - @apply border-border; 89 - } 90 - body { 91 - @apply bg-background text-foreground; 92 - } 88 + * { 89 + @apply border-border; 90 + } 91 + body { 92 + @apply bg-background text-foreground; 93 + } 93 94 }
+1 -1
apps/web/src/lib/utils.ts
··· 2 2 import { twMerge } from "tailwind-merge"; 3 3 4 4 export function cn(...inputs: ClassValue[]) { 5 - return twMerge(clsx(inputs)); 5 + return twMerge(clsx(inputs)); 6 6 }
+8 -8
apps/web/src/main.tsx
··· 8 8 9 9 const rootElement = document.getElementById("root") as HTMLElement; 10 10 if (!rootElement.innerHTML) { 11 - const root = createRoot(rootElement); 12 - root.render( 13 - <StrictMode> 14 - <QueryClientProvider client={queryClient}> 15 - <RouterProvider router={router} /> 16 - </QueryClientProvider> 17 - </StrictMode> 18 - ); 11 + const root = createRoot(rootElement); 12 + root.render( 13 + <StrictMode> 14 + <QueryClientProvider client={queryClient}> 15 + <RouterProvider router={router} /> 16 + </QueryClientProvider> 17 + </StrictMode>, 18 + ); 19 19 }
+9 -9
apps/web/src/pages/__root.tsx
··· 4 4 import { TanStackRouterDevtools } from "@tanstack/router-devtools"; 5 5 6 6 export const rootRoute = createRootRouteWithContext<{ 7 - queryClient: QueryClient; 7 + queryClient: QueryClient; 8 8 }>()({ 9 - component: RootComponent, 9 + component: RootComponent, 10 10 }); 11 11 12 12 function RootComponent() { 13 - return ( 14 - <> 15 - <Outlet /> 16 - <ReactQueryDevtools buttonPosition="top-right" /> 17 - <TanStackRouterDevtools position="bottom-right" /> 18 - </> 19 - ); 13 + return ( 14 + <> 15 + <Outlet /> 16 + <ReactQueryDevtools buttonPosition="top-right" /> 17 + <TanStackRouterDevtools position="bottom-right" /> 18 + </> 19 + ); 20 20 }
+25 -25
apps/web/src/pages/auth/index.tsx
··· 4 4 import { rootRoute } from "../__root"; 5 5 6 6 export const authIndexRoute = createRoute({ 7 - getParentRoute: () => rootRoute, 8 - path: "/auth", 9 - component: AuthIndexRouteComponent, 7 + getParentRoute: () => rootRoute, 8 + path: "/auth", 9 + component: AuthIndexRouteComponent, 10 10 }); 11 11 12 12 function AuthIndexRouteComponent() { 13 - return ( 14 - <div className="min-h-screen bg-gradient-to-b from-zinc-900 to-zinc-950 flex flex-col items-center justify-center p-4"> 15 - <motion.div 16 - initial={{ opacity: 0, y: 20 }} 17 - animate={{ opacity: 1, y: 0 }} 18 - className="w-full max-w-md" 19 - > 20 - <div className="mb-8 text-center"> 21 - <Logo className="mx-auto mb-6" /> 22 - <h1 className="text-3xl font-bold text-zinc-100 mb-2"> 23 - Welcome back 24 - </h1> 25 - <p className="text-zinc-400"> 26 - Enter your credentials to access your workspace 27 - </p> 28 - </div> 13 + return ( 14 + <div className="min-h-screen bg-gradient-to-b from-zinc-900 to-zinc-950 flex flex-col items-center justify-center p-4"> 15 + <motion.div 16 + initial={{ opacity: 0, y: 20 }} 17 + animate={{ opacity: 1, y: 0 }} 18 + className="w-full max-w-md" 19 + > 20 + <div className="mb-8 text-center"> 21 + <Logo className="mx-auto mb-6" /> 22 + <h1 className="text-3xl font-bold text-zinc-100 mb-2"> 23 + Welcome back 24 + </h1> 25 + <p className="text-zinc-400"> 26 + Enter your credentials to access your workspace 27 + </p> 28 + </div> 29 29 30 - <div className="bg-zinc-900/50 backdrop-blur-xl rounded-xl border border-zinc-800/50 p-6"> 31 - <Outlet /> 32 - </div> 33 - </motion.div> 34 - </div> 35 - ); 30 + <div className="bg-zinc-900/50 backdrop-blur-xl rounded-xl border border-zinc-800/50 p-6"> 31 + <Outlet /> 32 + </div> 33 + </motion.div> 34 + </div> 35 + ); 36 36 }
+16 -16
apps/web/src/pages/auth/sign-in.tsx
··· 5 5 import { rootRoute } from "../__root"; 6 6 7 7 export const signInRoute = createRoute({ 8 - getParentRoute: () => rootRoute, 9 - path: "/auth/sign-in", 10 - component: SignIn, 8 + getParentRoute: () => rootRoute, 9 + path: "/auth/sign-in", 10 + component: SignIn, 11 11 }); 12 12 13 13 function SignIn() { 14 - return ( 15 - <AuthLayout 16 - title="Welcome back" 17 - subtitle="Enter your credentials to access your workspace" 18 - > 19 - <SignInForm /> 20 - <AuthToggle 21 - message="Don't have an account?" 22 - linkText="Create account" 23 - linkTo="/auth/sign-up" 24 - /> 25 - </AuthLayout> 26 - ); 14 + return ( 15 + <AuthLayout 16 + title="Welcome back" 17 + subtitle="Enter your credentials to access your workspace" 18 + > 19 + <SignInForm /> 20 + <AuthToggle 21 + message="Don't have an account?" 22 + linkText="Create account" 23 + linkTo="/auth/sign-up" 24 + /> 25 + </AuthLayout> 26 + ); 27 27 }
+16 -16
apps/web/src/pages/auth/sign-up.tsx
··· 5 5 import { rootRoute } from "../__root"; 6 6 7 7 export const signUpRoute = createRoute({ 8 - getParentRoute: () => rootRoute, 9 - path: "/auth/sign-up", 10 - component: SignUp, 8 + getParentRoute: () => rootRoute, 9 + path: "/auth/sign-up", 10 + component: SignUp, 11 11 }); 12 12 13 13 function SignUp() { 14 - return ( 15 - <AuthLayout 16 - title="Create account" 17 - subtitle="Get started with your free workspace" 18 - > 19 - <SignInForm /> 20 - <AuthToggle 21 - message="Already have an account?" 22 - linkText="Sign in" 23 - linkTo="/auth/sign-in" 24 - /> 25 - </AuthLayout> 26 - ); 14 + return ( 15 + <AuthLayout 16 + title="Create account" 17 + subtitle="Get started with your free workspace" 18 + > 19 + <SignInForm /> 20 + <AuthToggle 21 + message="Already have an account?" 22 + linkText="Sign in" 23 + linkTo="/auth/sign-in" 24 + /> 25 + </AuthLayout> 26 + ); 27 27 }
+13 -13
apps/web/src/pages/index.tsx
··· 5 5 import { rootRoute } from "./__root"; 6 6 7 7 export const indexRoute = createRoute({ 8 - getParentRoute: () => rootRoute, 9 - path: "/", 10 - component: IndexRouteComponent, 8 + getParentRoute: () => rootRoute, 9 + path: "/", 10 + component: IndexRouteComponent, 11 11 }); 12 12 13 13 function IndexRouteComponent() { 14 - const { data } = useQuery({ 15 - queryKey: ["me"], 16 - queryFn: () => api.me.get(), 17 - }); 14 + const { data } = useQuery({ 15 + queryKey: ["me"], 16 + queryFn: () => api.me.get(), 17 + }); 18 18 19 - return ( 20 - <div className="flex h-screen flex-col md:flex-row bg-zinc-50 dark:bg-zinc-950"> 21 - <Sidebar /> 22 - <main className="flex-1 overflow-hidden p-6">{JSON.stringify(data)}</main> 23 - </div> 24 - ); 19 + return ( 20 + <div className="flex h-screen flex-col md:flex-row bg-zinc-50 dark:bg-zinc-950"> 21 + <Sidebar /> 22 + <main className="flex-1 overflow-hidden p-6">{JSON.stringify(data)}</main> 23 + </div> 24 + ); 25 25 }
+12 -12
apps/web/src/router/index.ts
··· 7 7 import queryClient from "../query-client"; 8 8 9 9 const routeTree = rootRoute.addChildren([ 10 - indexRoute.addChildren([ 11 - authIndexRoute.addChildren([signInRoute, signUpRoute]), 12 - ]), 10 + indexRoute.addChildren([ 11 + authIndexRoute.addChildren([signInRoute, signUpRoute]), 12 + ]), 13 13 ]); 14 14 15 15 const router = createRouter({ 16 - routeTree, 17 - defaultPreload: "intent", 18 - defaultPreloadStaleTime: 0, 19 - context: { 20 - queryClient, 21 - }, 16 + routeTree, 17 + defaultPreload: "intent", 18 + defaultPreloadStaleTime: 0, 19 + context: { 20 + queryClient, 21 + }, 22 22 }); 23 23 24 24 declare module "@tanstack/react-router" { 25 - interface Register { 26 - router: typeof router; 27 - } 25 + interface Register { 26 + router: typeof router; 27 + } 28 28 } 29 29 30 30 export default router;
+26 -26
apps/web/tsconfig.app.json
··· 1 1 { 2 - "compilerOptions": { 3 - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 - "target": "ES2020", 5 - "useDefineForClassFields": true, 6 - "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 - "module": "ESNext", 8 - "skipLibCheck": true, 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 + "target": "ES2020", 5 + "useDefineForClassFields": true, 6 + "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 + "module": "ESNext", 8 + "skipLibCheck": true, 9 9 10 - /* Bundler mode */ 11 - "moduleResolution": "bundler", 12 - "allowImportingTsExtensions": true, 13 - "isolatedModules": true, 14 - "moduleDetection": "force", 15 - "noEmit": true, 16 - "jsx": "react-jsx", 10 + /* Bundler mode */ 11 + "moduleResolution": "bundler", 12 + "allowImportingTsExtensions": true, 13 + "isolatedModules": true, 14 + "moduleDetection": "force", 15 + "noEmit": true, 16 + "jsx": "react-jsx", 17 17 18 - /* Linting */ 19 - "strict": true, 20 - "noUnusedLocals": true, 21 - "noUnusedParameters": true, 22 - "noFallthroughCasesInSwitch": true, 23 - "noUncheckedSideEffectImports": true, 24 - "paths": { 25 - "@/*": ["./src/*"] 26 - } 27 - }, 28 - "include": ["src"], 29 - "baseUrl": "." 18 + /* Linting */ 19 + "strict": true, 20 + "noUnusedLocals": true, 21 + "noUnusedParameters": true, 22 + "noFallthroughCasesInSwitch": true, 23 + "noUncheckedSideEffectImports": true, 24 + "paths": { 25 + "@/*": ["./src/*"] 26 + } 27 + }, 28 + "include": ["src"], 29 + "baseUrl": "." 30 30 }
+11 -11
apps/web/tsconfig.json
··· 1 1 { 2 - "files": [], 3 - "references": [ 4 - { "path": "./tsconfig.app.json" }, 5 - { "path": "./tsconfig.node.json" } 6 - ], 7 - "compilerOptions": { 8 - "baseUrl": ".", 9 - "paths": { 10 - "@/*": ["./src/*"] 11 - } 12 - } 2 + "files": [], 3 + "references": [ 4 + { "path": "./tsconfig.app.json" }, 5 + { "path": "./tsconfig.node.json" } 6 + ], 7 + "compilerOptions": { 8 + "baseUrl": ".", 9 + "paths": { 10 + "@/*": ["./src/*"] 11 + } 12 + } 13 13 }
+20 -20
apps/web/tsconfig.node.json
··· 1 1 { 2 - "compilerOptions": { 3 - "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 - "target": "ES2022", 5 - "lib": ["ES2023"], 6 - "module": "ESNext", 7 - "skipLibCheck": true, 2 + "compilerOptions": { 3 + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 + "target": "ES2022", 5 + "lib": ["ES2023"], 6 + "module": "ESNext", 7 + "skipLibCheck": true, 8 8 9 - /* Bundler mode */ 10 - "moduleResolution": "bundler", 11 - "allowImportingTsExtensions": true, 12 - "isolatedModules": true, 13 - "moduleDetection": "force", 14 - "noEmit": true, 9 + /* Bundler mode */ 10 + "moduleResolution": "bundler", 11 + "allowImportingTsExtensions": true, 12 + "isolatedModules": true, 13 + "moduleDetection": "force", 14 + "noEmit": true, 15 15 16 - /* Linting */ 17 - "strict": true, 18 - "noUnusedLocals": true, 19 - "noUnusedParameters": true, 20 - "noFallthroughCasesInSwitch": true, 21 - "noUncheckedSideEffectImports": true 22 - }, 23 - "include": ["vite.config.ts"] 16 + /* Linting */ 17 + "strict": true, 18 + "noUnusedLocals": true, 19 + "noUnusedParameters": true, 20 + "noFallthroughCasesInSwitch": true, 21 + "noUncheckedSideEffectImports": true 22 + }, 23 + "include": ["vite.config.ts"] 24 24 }
+9 -9
apps/web/vite.config.ts
··· 5 5 6 6 // https://vite.dev/config/ 7 7 export default defineConfig({ 8 - plugins: [TanStackRouterVite(), react()], 9 - server: { 10 - host: true, 11 - }, 12 - resolve: { 13 - alias: { 14 - "@": path.resolve(__dirname, "./src"), 15 - }, 16 - }, 8 + plugins: [TanStackRouterVite(), react()], 9 + server: { 10 + host: true, 11 + }, 12 + resolve: { 13 + alias: { 14 + "@": path.resolve(__dirname, "./src"), 15 + }, 16 + }, 17 17 });
+28 -28
biome.json
··· 1 1 { 2 - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 - "vcs": { 4 - "enabled": false, 5 - "clientKind": "git", 6 - "useIgnoreFile": false 7 - }, 8 - "files": { 9 - "ignoreUnknown": false, 10 - "ignore": [] 11 - }, 12 - "formatter": { 13 - "enabled": true, 14 - "indentStyle": "tab" 15 - }, 16 - "organizeImports": { 17 - "enabled": true 18 - }, 19 - "linter": { 20 - "enabled": true, 21 - "rules": { 22 - "recommended": true 23 - } 24 - }, 25 - "javascript": { 26 - "formatter": { 27 - "quoteStyle": "double" 28 - } 29 - } 2 + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 + "vcs": { 4 + "enabled": false, 5 + "clientKind": "git", 6 + "useIgnoreFile": false 7 + }, 8 + "files": { 9 + "ignoreUnknown": false, 10 + "ignore": ["**/tsconfig*.json"] 11 + }, 12 + "formatter": { 13 + "enabled": true, 14 + "indentStyle": "tab" 15 + }, 16 + "organizeImports": { 17 + "enabled": true 18 + }, 19 + "linter": { 20 + "enabled": true, 21 + "rules": { 22 + "recommended": true 23 + } 24 + }, 25 + "javascript": { 26 + "formatter": { 27 + "quoteStyle": "double" 28 + } 29 + } 30 30 }
+24 -27
package.json
··· 1 1 { 2 - "name": "kaneo", 3 - "private": true, 4 - "scripts": { 5 - "build": "turbo build", 6 - "dev": "turbo dev", 7 - "format-and-lint": "biome check .", 8 - "format-and-lint:fix": "biome check . --write" 9 - }, 10 - "devDependencies": { 11 - "@biomejs/biome": "1.9.4", 12 - "@tanstack/router-devtools": "^1.94.1", 13 - "@tanstack/router-plugin": "^1.94.1", 14 - "prettier": "^3.2.5", 15 - "turbo": "^2.3.3", 16 - "typescript": "5.5.4" 17 - }, 18 - "engines": { 19 - "node": ">=18" 20 - }, 21 - "packageManager": "pnpm@7.5.1", 22 - "workspaces": [ 23 - "apps/*", 24 - "packages/*" 25 - ], 26 - "dependencies": { 27 - "@tanstack/react-router": "^1.94.1" 28 - } 2 + "name": "kaneo", 3 + "private": true, 4 + "scripts": { 5 + "build": "turbo build", 6 + "dev": "turbo dev", 7 + "format-and-lint": "biome check .", 8 + "format-and-lint:fix": "biome check . --write" 9 + }, 10 + "devDependencies": { 11 + "@biomejs/biome": "1.9.4", 12 + "@tanstack/router-devtools": "^1.94.1", 13 + "@tanstack/router-plugin": "^1.94.1", 14 + "prettier": "^3.2.5", 15 + "turbo": "^2.3.3", 16 + "typescript": "5.5.4" 17 + }, 18 + "engines": { 19 + "node": ">=18" 20 + }, 21 + "packageManager": "pnpm@7.5.1", 22 + "workspaces": ["apps/*", "packages/*"], 23 + "dependencies": { 24 + "@tanstack/react-router": "^1.94.1" 25 + } 29 26 }
+3 -3
packages/libs/src/eden.ts
··· 2 2 import type { App } from "@kaneo/api"; 3 3 4 4 export const api = treaty<App>("http://localhost:1337", { 5 - fetch: { 6 - credentials: "include", 7 - }, 5 + fetch: { 6 + credentials: "include", 7 + }, 8 8 });
+17 -17
packages/typescript-config/base.json
··· 1 1 { 2 - "$schema": "https://json.schemastore.org/tsconfig", 3 - "compilerOptions": { 4 - "declaration": true, 5 - "declarationMap": true, 6 - "esModuleInterop": true, 7 - "incremental": false, 8 - "isolatedModules": true, 9 - "lib": ["es2022", "DOM", "DOM.Iterable"], 10 - "module": "NodeNext", 11 - "moduleDetection": "force", 12 - "moduleResolution": "NodeNext", 13 - "noUncheckedIndexedAccess": true, 14 - "resolveJsonModule": true, 15 - "skipLibCheck": true, 16 - "strict": true, 17 - "target": "ES2022" 18 - } 2 + "$schema": "https://json.schemastore.org/tsconfig", 3 + "compilerOptions": { 4 + "declaration": true, 5 + "declarationMap": true, 6 + "esModuleInterop": true, 7 + "incremental": false, 8 + "isolatedModules": true, 9 + "lib": ["es2022", "DOM", "DOM.Iterable"], 10 + "module": "NodeNext", 11 + "moduleDetection": "force", 12 + "moduleResolution": "NodeNext", 13 + "noUncheckedIndexedAccess": true, 14 + "resolveJsonModule": true, 15 + "skipLibCheck": true, 16 + "strict": true, 17 + "target": "ES2022" 18 + } 19 19 }
+7 -7
packages/typescript-config/package.json
··· 1 1 { 2 - "name": "@kaneo/typescript-config", 3 - "version": "0.0.0", 4 - "private": true, 5 - "license": "MIT", 6 - "publishConfig": { 7 - "access": "public" 8 - } 2 + "name": "@kaneo/typescript-config", 3 + "version": "0.0.0", 4 + "private": true, 5 + "license": "MIT", 6 + "publishConfig": { 7 + "access": "public" 8 + } 9 9 }
+5 -5
packages/typescript-config/react-library.json
··· 1 1 { 2 - "$schema": "https://json.schemastore.org/tsconfig", 3 - "extends": "./base.json", 4 - "compilerOptions": { 5 - "jsx": "react-jsx" 6 - } 2 + "$schema": "https://json.schemastore.org/tsconfig", 3 + "extends": "./base.json", 4 + "compilerOptions": { 5 + "jsx": "react-jsx" 6 + } 7 7 }