socket io stuff entered
This commit is contained in:
1
.vscode/lst.code-snippets
vendored
1
.vscode/lst.code-snippets
vendored
@@ -10,6 +10,7 @@
|
|||||||
"\tmessage: \"${5:Failed to connect to the prod sql server.}\",",
|
"\tmessage: \"${5:Failed to connect to the prod sql server.}\",",
|
||||||
"\tdata: ${6:[]},",
|
"\tdata: ${6:[]},",
|
||||||
"\tnotify: ${7:false},",
|
"\tnotify: ${7:false},",
|
||||||
|
"\troom: ${8:''},",
|
||||||
"});"
|
"});"
|
||||||
],
|
],
|
||||||
"description": "Insert a returnFunc template"
|
"description": "Insert a returnFunc template"
|
||||||
|
|||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
@@ -62,6 +62,7 @@
|
|||||||
"opendock",
|
"opendock",
|
||||||
"opendocks",
|
"opendocks",
|
||||||
"ppoo",
|
"ppoo",
|
||||||
|
"preseed",
|
||||||
"prodlabels",
|
"prodlabels",
|
||||||
"prolink",
|
"prolink",
|
||||||
"trycatch"
|
"trycatch"
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { Writable } from "node:stream";
|
import { Writable } from "node:stream";
|
||||||
|
|
||||||
import pino, { type Logger } from "pino";
|
import pino, { type Logger } from "pino";
|
||||||
import { db } from "../db/db.controller.js";
|
import { db } from "../db/db.controller.js";
|
||||||
import { logs } from "../db/schema/logs.schema.js";
|
import { logs } from "../db/schema/logs.schema.js";
|
||||||
|
import { emitToRoom } from "../socket.io/roomEmitter.socket.js";
|
||||||
import { tryCatch } from "../utils/trycatch.utils.js";
|
import { tryCatch } from "../utils/trycatch.utils.js";
|
||||||
//import build from "pino-abstract-transport";
|
//import build from "pino-abstract-transport";
|
||||||
|
|
||||||
@@ -40,6 +42,10 @@ const dbStream = new Writable({
|
|||||||
console.error(res.error);
|
console.error(res.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (obj.room) {
|
||||||
|
emitToRoom(obj.room, obj);
|
||||||
|
}
|
||||||
|
emitToRoom("logs", obj);
|
||||||
callback();
|
callback();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("DB log insert error:", err);
|
console.error("DB log insert error:", err);
|
||||||
@@ -48,31 +54,34 @@ const dbStream = new Writable({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ✅ Multistream setup
|
|
||||||
const streams = [
|
|
||||||
{
|
|
||||||
stream: pino.transport({
|
|
||||||
target: "pino-pretty",
|
|
||||||
options: {
|
|
||||||
colorize: true,
|
|
||||||
singleLine: true,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
level: "info",
|
|
||||||
stream: dbStream,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const rootLogger: Logger = pino(
|
const rootLogger: Logger = pino(
|
||||||
{
|
{
|
||||||
level: logLevel,
|
level: logLevel,
|
||||||
redact: { paths: ["email", "password"], remove: true },
|
redact: { paths: ["email", "password"], remove: true },
|
||||||
},
|
},
|
||||||
pino.multistream(streams),
|
pino.multistream([
|
||||||
|
{
|
||||||
|
level: logLevel,
|
||||||
|
stream: pino.transport({
|
||||||
|
target: "pino-pretty",
|
||||||
|
options: {
|
||||||
|
colorize: true,
|
||||||
|
singleLine: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
level: logLevel,
|
||||||
|
stream: dbStream,
|
||||||
|
},
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* example data to put in as a reference
|
||||||
|
* rooms logs | labels | etc
|
||||||
|
*/
|
||||||
export const createLogger = (bindings: Record<string, unknown>): Logger => {
|
export const createLogger = (bindings: Record<string, unknown>): Logger => {
|
||||||
return rootLogger.child(bindings);
|
return rootLogger.child(bindings);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ const postRelease = async (release: Releases) => {
|
|||||||
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
loadTypeId: process.env.DEFAULT_LOAD_TYPE, // well get this and make it a default one
|
||||||
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
dockId: process.env.DEFAULT_DOCK, // this the warehouse we want it in to start out
|
||||||
refNumbers: [release.ReleaseNumber],
|
refNumbers: [release.ReleaseNumber],
|
||||||
refNumber: release.ReleaseNumber,
|
//refNumber: release.ReleaseNumber,
|
||||||
start: release.DeliveryDate,
|
start: release.DeliveryDate,
|
||||||
end: addHours(release.DeliveryDate, 1),
|
end: addHours(release.DeliveryDate, 1),
|
||||||
notes: "",
|
notes: "",
|
||||||
@@ -202,14 +202,17 @@ const postRelease = async (release: Releases) => {
|
|||||||
|
|
||||||
log.info({}, `${release.ReleaseNumber} was updated`);
|
log.info({}, `${release.ReleaseNumber} was updated`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.error({ error: e }, "Error updating the release");
|
log.error(
|
||||||
|
{ error: e },
|
||||||
|
`Error updating the release: ${release.ReleaseNumber}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
// biome-ignore lint/suspicious/noExplicitAny: to many possibilities
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
//console.info(newDockApt);
|
//console.info(newDockApt);
|
||||||
log.error(
|
log.error(
|
||||||
{ error: e.response.data },
|
{ error: e.response.data },
|
||||||
"An error has occurred during patching of the release",
|
`An error has occurred during patching of the release: ${release.ReleaseNumber}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -15,6 +15,12 @@ import { createCronJob } from "./utils/croner.utils.js";
|
|||||||
const port = Number(process.env.PORT) || 3000;
|
const port = Number(process.env.PORT) || 3000;
|
||||||
export let systemSettings: Setting[] = [];
|
export let systemSettings: Setting[] = [];
|
||||||
const start = async () => {
|
const start = async () => {
|
||||||
|
const { app, baseUrl } = await createApp();
|
||||||
|
|
||||||
|
const server = createServer(app);
|
||||||
|
|
||||||
|
setupSocketIORoutes(baseUrl, server);
|
||||||
|
|
||||||
const log = createLogger({ module: "system", subModule: "main start" });
|
const log = createLogger({ module: "system", subModule: "main start" });
|
||||||
|
|
||||||
// triggering long lived processes
|
// triggering long lived processes
|
||||||
@@ -25,6 +31,7 @@ const start = async () => {
|
|||||||
systemSettings = await db.select().from(settings);
|
systemSettings = await db.select().from(settings);
|
||||||
|
|
||||||
//when starting up long lived features the name must match the setting name.
|
//when starting up long lived features the name must match the setting name.
|
||||||
|
// also we always want to have long lived processes inside a setting check.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
|
if (systemSettings.filter((n) => n.name === "opendock_sync")[0]?.active) {
|
||||||
log.info({}, "Opendock is not active");
|
log.info({}, "Opendock is not active");
|
||||||
@@ -35,19 +42,13 @@ const start = async () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup sql jobs
|
// these jobs below are system jobs and should run no matter what.
|
||||||
createCronJob("JobAuditLogCleanUp", "0 0 5 * * *", () =>
|
createCronJob("JobAuditLogCleanUp", "0 0 5 * * *", () =>
|
||||||
dbCleanup("jobs", 30),
|
dbCleanup("jobs", 30),
|
||||||
);
|
);
|
||||||
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
createCronJob("logsCleanup", "0 15 5 * * *", () => dbCleanup("logs", 120));
|
||||||
}, 5 * 1000);
|
}, 5 * 1000);
|
||||||
|
|
||||||
const { app, baseUrl } = await createApp();
|
|
||||||
|
|
||||||
const server = createServer(app);
|
|
||||||
|
|
||||||
setupSocketIORoutes(baseUrl, server);
|
|
||||||
|
|
||||||
server.listen(port, async () => {
|
server.listen(port, async () => {
|
||||||
log.info(
|
log.info(
|
||||||
`Listening on http://${os.hostname()}:${port}${baseUrl}, logging in ${process.env.LOG_LEVEL}, current ENV ${process.env.NODE_ENV ? process.env.NODE_ENV : "development"}`,
|
`Listening on http://${os.hostname()}:${port}${baseUrl}, logging in ${process.env.LOG_LEVEL}, current ENV ${process.env.NODE_ENV ? process.env.NODE_ENV : "development"}`,
|
||||||
|
|||||||
8
backend/socket.io/roomCache.socket.ts
Normal file
8
backend/socket.io/roomCache.socket.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { RoomId } from "./types.socket.js";
|
||||||
|
|
||||||
|
export const MAX_HISTORY = 20;
|
||||||
|
export const FLUSH_INTERVAL = 100; // 50ms change higher if needed
|
||||||
|
|
||||||
|
export const roomHistory = new Map<RoomId, unknown[]>();
|
||||||
|
export const roomBuffers = new Map<RoomId, any[]>();
|
||||||
|
export const roomFlushTimers = new Map<RoomId, NodeJS.Timeout>();
|
||||||
33
backend/socket.io/roomDefinitions.socket.ts
Normal file
33
backend/socket.io/roomDefinitions.socket.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { logs } from "backend/db/schema/logs.schema.js";
|
||||||
|
import { desc } from "drizzle-orm";
|
||||||
|
import { db } from "../db/db.controller.js";
|
||||||
|
import type { RoomId } from "./types.socket.js";
|
||||||
|
|
||||||
|
type RoomDefinition<T = unknown> = {
|
||||||
|
seed: (limit: number) => Promise<T[]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const roomDefinition: Record<RoomId, RoomDefinition> = {
|
||||||
|
logs: {
|
||||||
|
seed: async (limit) => {
|
||||||
|
try {
|
||||||
|
const rows = await db
|
||||||
|
.select()
|
||||||
|
.from(logs)
|
||||||
|
.orderBy(desc(logs.createdAt))
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
return rows.reverse();
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to seed logs:", e);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
labels: {
|
||||||
|
seed: async (limit) => {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
27
backend/socket.io/roomEmitter.socket.ts
Normal file
27
backend/socket.io/roomEmitter.socket.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// the emitter setup
|
||||||
|
|
||||||
|
import type { RoomId } from "./types.socket.js";
|
||||||
|
|
||||||
|
let addDataToRoom: ((roomId: RoomId, payload: unknown[]) => void) | null = null;
|
||||||
|
|
||||||
|
export const registerEmitter = (
|
||||||
|
fn: (roomId: RoomId, payload: unknown[]) => void,
|
||||||
|
) => {
|
||||||
|
addDataToRoom = fn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const emitToRoom = (roomId: RoomId, payload: unknown[]) => {
|
||||||
|
if (!addDataToRoom) {
|
||||||
|
console.error("Socket emitter not initialized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDataToRoom(roomId, payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
import { emitToRoom } from "../socket/socketEmitter.js";
|
||||||
|
// room name
|
||||||
|
// its payload
|
||||||
|
emitToRoom("logs", newLogRow);
|
||||||
|
*/
|
||||||
73
backend/socket.io/roomService.socket.ts
Normal file
73
backend/socket.io/roomService.socket.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type { Server } from "socket.io";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import {
|
||||||
|
FLUSH_INTERVAL,
|
||||||
|
MAX_HISTORY,
|
||||||
|
roomBuffers,
|
||||||
|
roomFlushTimers,
|
||||||
|
roomHistory,
|
||||||
|
} from "./roomCache.socket.js";
|
||||||
|
import { roomDefinition } from "./roomDefinitions.socket.js";
|
||||||
|
import type { RoomId } from "./types.socket.js";
|
||||||
|
|
||||||
|
// get the db data if not exiting already
|
||||||
|
const log = createLogger({ module: "socket.io", subModule: "roomService" });
|
||||||
|
|
||||||
|
export const preseedRoom = async (roomId: RoomId) => {
|
||||||
|
if (roomHistory.has(roomId)) {
|
||||||
|
return roomHistory.get(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const roomDef = roomDefinition[roomId];
|
||||||
|
|
||||||
|
if (!roomDef) {
|
||||||
|
log.error({}, `Room ${roomId} is not defined`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const latestData = await roomDef.seed(MAX_HISTORY);
|
||||||
|
|
||||||
|
roomHistory.set(roomId, latestData);
|
||||||
|
|
||||||
|
return latestData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRoomEmitter = (io: Server) => {
|
||||||
|
const addDataToRoom = <T>(roomId: RoomId, payload: T) => {
|
||||||
|
if (!roomHistory.has(roomId)) {
|
||||||
|
roomHistory.set(roomId, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
const history = roomHistory.get(roomId)!;
|
||||||
|
history?.push(payload);
|
||||||
|
|
||||||
|
if (history?.length > MAX_HISTORY) {
|
||||||
|
history?.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!roomBuffers.has(roomId)) {
|
||||||
|
roomBuffers.set(roomId, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
roomBuffers.get(roomId)!.push(payload);
|
||||||
|
|
||||||
|
if (!roomFlushTimers.has(roomId)) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
const buffered = roomBuffers.get(roomId) || [];
|
||||||
|
|
||||||
|
if (buffered.length > 0) {
|
||||||
|
io.to(roomId).emit("room-update", {
|
||||||
|
roomId,
|
||||||
|
payloads: buffered, // ✅ array now
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
roomBuffers.set(roomId, []);
|
||||||
|
roomFlushTimers.delete(roomId);
|
||||||
|
}, FLUSH_INTERVAL);
|
||||||
|
|
||||||
|
roomFlushTimers.set(roomId, timer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return { addDataToRoom };
|
||||||
|
};
|
||||||
@@ -3,9 +3,13 @@ import type { Server as HttpServer } from "node:http";
|
|||||||
//import { fileURLToPath } from "node:url";
|
//import { fileURLToPath } from "node:url";
|
||||||
import { instrument } from "@socket.io/admin-ui";
|
import { instrument } from "@socket.io/admin-ui";
|
||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
|
import { createLogger } from "../logger/logger.controller.js";
|
||||||
|
import { registerEmitter } from "./roomEmitter.socket.js";
|
||||||
|
import { createRoomEmitter, preseedRoom } from "./roomService.socket.js";
|
||||||
|
|
||||||
//const __filename = fileURLToPath(import.meta.url);
|
//const __filename = fileURLToPath(import.meta.url);
|
||||||
//const __dirname = dirname(__filename);
|
//const __dirname = dirname(__filename);
|
||||||
|
const log = createLogger({ module: "socket.io", subModule: "setup" });
|
||||||
|
|
||||||
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
||||||
const io = new Server(server, {
|
const io = new Server(server, {
|
||||||
@@ -16,29 +20,49 @@ export const setupSocketIORoutes = (baseUrl: string, server: HttpServer) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ✅ Create emitter instance
|
||||||
|
const { addDataToRoom } = createRoomEmitter(io);
|
||||||
|
registerEmitter(addDataToRoom);
|
||||||
|
|
||||||
io.on("connection", (s) => {
|
io.on("connection", (s) => {
|
||||||
console.info(s.id);
|
log.info({}, `User connected: ${s.id}`);
|
||||||
|
|
||||||
|
s.emit("welcome", {
|
||||||
|
serverTime: Date.now(),
|
||||||
|
availableRooms: ["logs", "labels"],
|
||||||
|
version: "1.0.0",
|
||||||
|
});
|
||||||
|
|
||||||
|
s.on("join-room", async (rn) => {
|
||||||
|
s.join(rn);
|
||||||
|
|
||||||
|
// get room seeded
|
||||||
|
const history = await preseedRoom(rn);
|
||||||
|
|
||||||
|
// send the intial data
|
||||||
|
s.emit("room-update", {
|
||||||
|
roomId: rn,
|
||||||
|
payloads: history,
|
||||||
|
initial: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// admin stuff for socket io
|
io.on("disconnect", (s) => {
|
||||||
// app.use(
|
log.info({}, "User disconnected:", s.id);
|
||||||
// express.static(
|
});
|
||||||
// join(__dirname, "../../../node_modules/@socket.io/admin-ui/dist"),
|
|
||||||
// ),
|
// admin stuff
|
||||||
// );
|
|
||||||
|
|
||||||
// app.get(baseUrl + "/admindashboard", (_, res) => {
|
|
||||||
// res.sendFile(
|
|
||||||
// join(
|
|
||||||
// __dirname,
|
|
||||||
// "../../../node_modules/@socket.io/admin-ui/dist/index.js",
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
const admin = io.of("/admin");
|
const admin = io.of("/admin");
|
||||||
admin.on("connection", () => {
|
admin.on("connection", (s) => {
|
||||||
console.info("Connected to admin userspace");
|
log.info({}, `User connected: ${s.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
admin.on("disconnect", (s) => {
|
||||||
|
log.info({}, "User disconnected:", s.id);
|
||||||
|
});
|
||||||
|
|
||||||
instrument(io, {
|
instrument(io, {
|
||||||
auth: false,
|
auth: false,
|
||||||
//namespaceName: "/admin",
|
//namespaceName: "/admin",
|
||||||
|
|||||||
1
backend/socket.io/types.socket.ts
Normal file
1
backend/socket.io/types.socket.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export type RoomId = "logs" | "labels"; //| "alerts" | "metrics";
|
||||||
@@ -24,6 +24,7 @@ export const featureControl = async (data: Setting) => {
|
|||||||
stopCronJob(data.name);
|
stopCronJob(data.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// specific setting stuff should have handled like below. what needs turned back on or off.
|
||||||
if (data.name === "opendock_sync" && data.active) {
|
if (data.name === "opendock_sync" && data.active) {
|
||||||
opendockSocketMonitor();
|
opendockSocketMonitor();
|
||||||
monitorReleaseChanges();
|
monitorReleaseChanges();
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ interface Data<T = unknown[]> {
|
|||||||
| "settings";
|
| "settings";
|
||||||
level: "info" | "error" | "debug" | "fatal";
|
level: "info" | "error" | "debug" | "fatal";
|
||||||
message: string;
|
message: string;
|
||||||
|
room?: string;
|
||||||
data?: T;
|
data?: T;
|
||||||
notify?: boolean;
|
notify?: boolean;
|
||||||
}
|
}
|
||||||
@@ -38,20 +39,21 @@ interface Data<T = unknown[]> {
|
|||||||
*/
|
*/
|
||||||
export const returnFunc = (data: Data) => {
|
export const returnFunc = (data: Data) => {
|
||||||
const notify = data.notify ? data.notify : false;
|
const notify = data.notify ? data.notify : false;
|
||||||
|
const room = data.room ?? data.room;
|
||||||
const log = createLogger({ module: data.module, subModule: data.subModule });
|
const log = createLogger({ module: data.module, subModule: data.subModule });
|
||||||
// handle the logging part
|
// handle the logging part
|
||||||
switch (data.level) {
|
switch (data.level) {
|
||||||
case "info":
|
case "info":
|
||||||
log.info({ notify: notify }, data.message);
|
log.info({ notify: notify, room }, data.message);
|
||||||
break;
|
break;
|
||||||
case "error":
|
case "error":
|
||||||
log.error({ notify: notify, error: data.data }, data.message);
|
log.error({ notify: notify, error: data.data, room }, data.message);
|
||||||
break;
|
break;
|
||||||
case "debug":
|
case "debug":
|
||||||
log.debug({ notify: notify }, data.message);
|
log.debug({ notify: notify, room }, data.message);
|
||||||
break;
|
break;
|
||||||
case "fatal":
|
case "fatal":
|
||||||
log.fatal({ notify: notify }, data.message);
|
log.fatal({ notify: notify, room }, data.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// api section to return
|
// api section to return
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"projectName": "frontend",
|
|
||||||
"mode": "file-router",
|
|
||||||
"typescript": true,
|
|
||||||
"packageManager": "npm",
|
|
||||||
"includeExamples": false,
|
|
||||||
"tailwind": true,
|
|
||||||
"addOnOptions": {},
|
|
||||||
"envVarValues": {},
|
|
||||||
"git": false,
|
|
||||||
"routerOnly": false,
|
|
||||||
"version": 1,
|
|
||||||
"framework": "react-cra",
|
|
||||||
"chosenAddOns": [
|
|
||||||
"biome",
|
|
||||||
"shadcn",
|
|
||||||
"better-auth",
|
|
||||||
"tanstack-query"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
# shadcn instructions
|
|
||||||
|
|
||||||
Use the latest version of Shadcn to install new components, like this command to add a button component:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx shadcn@latest add button
|
|
||||||
```
|
|
||||||
13
frontend/.gitignore
vendored
13
frontend/.gitignore
vendored
@@ -1,13 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
dist
|
|
||||||
dist-ssr
|
|
||||||
*.local
|
|
||||||
.env
|
|
||||||
.nitro
|
|
||||||
.tanstack
|
|
||||||
.wrangler
|
|
||||||
.output
|
|
||||||
.vinxi
|
|
||||||
__unconfig*
|
|
||||||
todos.json
|
|
||||||
35
frontend/.vscode/settings.json
vendored
35
frontend/.vscode/settings.json
vendored
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"files.watcherExclude": {
|
|
||||||
"**/routeTree.gen.ts": true
|
|
||||||
},
|
|
||||||
"search.exclude": {
|
|
||||||
"**/routeTree.gen.ts": true
|
|
||||||
},
|
|
||||||
"files.readonlyInclude": {
|
|
||||||
"**/routeTree.gen.ts": true
|
|
||||||
},
|
|
||||||
"[javascript]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[javascriptreact]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[typescript]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[typescriptreact]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[json]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[jsonc]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"[css]": {
|
|
||||||
"editor.defaultFormatter": "biomejs.biome"
|
|
||||||
},
|
|
||||||
"editor.codeActionsOnSave": {
|
|
||||||
"source.organizeImports.biome": "explicit"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
Welcome to your new TanStack Start app!
|
|
||||||
|
|
||||||
# Getting Started
|
|
||||||
|
|
||||||
To run this application:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
# Building For Production
|
|
||||||
|
|
||||||
To build this application for production:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run test
|
|
||||||
```
|
|
||||||
|
|
||||||
## Styling
|
|
||||||
|
|
||||||
This project uses [Tailwind CSS](https://tailwindcss.com/) for styling.
|
|
||||||
|
|
||||||
### Removing Tailwind CSS
|
|
||||||
|
|
||||||
If you prefer not to use Tailwind CSS:
|
|
||||||
|
|
||||||
1. Remove the demo pages in `src/routes/demo/`
|
|
||||||
2. Replace the Tailwind import in `src/styles.css` with your own styles
|
|
||||||
3. Remove `tailwindcss()` from the plugins array in `vite.config.ts`
|
|
||||||
4. Uninstall the packages: `npm install @tailwindcss/vite tailwindcss -D`
|
|
||||||
|
|
||||||
## Linting & Formatting
|
|
||||||
|
|
||||||
This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following scripts are available:
|
|
||||||
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run lint
|
|
||||||
npm run format
|
|
||||||
npm run check
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Shadcn
|
|
||||||
|
|
||||||
Add components using the latest version of [Shadcn](https://ui.shadcn.com/).
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm dlx shadcn@latest add button
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Setting up Better Auth
|
|
||||||
|
|
||||||
1. Generate and set the `BETTER_AUTH_SECRET` environment variable in your `.env.local`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx -y @better-auth/cli secret
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Visit the [Better Auth documentation](https://www.better-auth.com) to unlock the full potential of authentication in your app.
|
|
||||||
|
|
||||||
### Adding a Database (Optional)
|
|
||||||
|
|
||||||
Better Auth can work in stateless mode, but to persist user data, add a database:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/lib/auth.ts
|
|
||||||
import { betterAuth } from "better-auth";
|
|
||||||
import { Pool } from "pg";
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
|
||||||
database: new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
}),
|
|
||||||
// ... rest of config
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run migrations:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx -y @better-auth/cli migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
|
|
||||||
This project uses [TanStack Router](https://tanstack.com/router) with file-based routing. Routes are managed as files in `src/routes`.
|
|
||||||
|
|
||||||
### Adding A Route
|
|
||||||
|
|
||||||
To add a new route to your application just add a new file in the `./src/routes` directory.
|
|
||||||
|
|
||||||
TanStack will automatically generate the content of the route file for you.
|
|
||||||
|
|
||||||
Now that you have two routes you can use a `Link` component to navigate between them.
|
|
||||||
|
|
||||||
### Adding Links
|
|
||||||
|
|
||||||
To use SPA (Single Page Application) navigation you will need to import the `Link` component from `@tanstack/react-router`.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
```
|
|
||||||
|
|
||||||
Then anywhere in your JSX you can use it like so:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a link that will navigate to the `/about` route.
|
|
||||||
|
|
||||||
More information on the `Link` component can be found in the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent).
|
|
||||||
|
|
||||||
### Using A Layout
|
|
||||||
|
|
||||||
In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. Anything you add to the root route will appear in all the routes. The route content will appear in the JSX where you render `{children}` in the `shellComponent`.
|
|
||||||
|
|
||||||
Here is an example layout that includes a header:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createRootRoute({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{ charSet: 'utf-8' },
|
|
||||||
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
||||||
{ title: 'My App' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
shellComponent: ({ children }) => (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<header>
|
|
||||||
<nav>
|
|
||||||
<Link to="/">Home</Link>
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
{children}
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
),
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
More information on layouts can be found in the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts).
|
|
||||||
|
|
||||||
## Server Functions
|
|
||||||
|
|
||||||
TanStack Start provides server functions that allow you to write server-side code that seamlessly integrates with your client components.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createServerFn } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
const getServerTime = createServerFn({
|
|
||||||
method: 'GET',
|
|
||||||
}).handler(async () => {
|
|
||||||
return new Date().toISOString()
|
|
||||||
})
|
|
||||||
|
|
||||||
// Use in a component
|
|
||||||
function MyComponent() {
|
|
||||||
const [time, setTime] = useState('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
getServerTime().then(setTime)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return <div>Server time: {time}</div>
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## API Routes
|
|
||||||
|
|
||||||
You can create API routes by using the `server` property in your route definitions:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
import { json } from '@tanstack/react-start'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/api/hello')({
|
|
||||||
server: {
|
|
||||||
handlers: {
|
|
||||||
GET: () => json({ message: 'Hello, World!' }),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Data Fetching
|
|
||||||
|
|
||||||
There are multiple ways to fetch data in your application. You can use TanStack Query to fetch data from a server. But you can also use the `loader` functionality built into TanStack Router to load the data for a route before it's rendered.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/people')({
|
|
||||||
loader: async () => {
|
|
||||||
const response = await fetch('https://swapi.dev/api/people')
|
|
||||||
return response.json()
|
|
||||||
},
|
|
||||||
component: PeopleComponent,
|
|
||||||
})
|
|
||||||
|
|
||||||
function PeopleComponent() {
|
|
||||||
const data = Route.useLoaderData()
|
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{data.results.map((person) => (
|
|
||||||
<li key={person.name}>{person.name}</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Loaders simplify your data fetching logic dramatically. Check out more information in the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters).
|
|
||||||
|
|
||||||
# Demo files
|
|
||||||
|
|
||||||
Files prefixed with `demo` can be safely deleted. They are there to provide a starting point for you to play around with the features you've installed.
|
|
||||||
|
|
||||||
# Learn More
|
|
||||||
|
|
||||||
You can learn more about all of the offerings from TanStack in the [TanStack documentation](https://tanstack.com).
|
|
||||||
|
|
||||||
For TanStack Start specific documentation, visit [TanStack Start](https://tanstack.com/start).
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://biomejs.dev/schemas/2.2.4/schema.json",
|
|
||||||
"vcs": {
|
|
||||||
"enabled": false,
|
|
||||||
"clientKind": "git",
|
|
||||||
"useIgnoreFile": false
|
|
||||||
},
|
|
||||||
"files": {
|
|
||||||
"ignoreUnknown": false,
|
|
||||||
"includes": [
|
|
||||||
"**/src/**/*",
|
|
||||||
"**/.vscode/**/*",
|
|
||||||
"**/index.html",
|
|
||||||
"**/vite.config.ts",
|
|
||||||
"!**/src/routeTree.gen.ts",
|
|
||||||
"!**/src/styles.css"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"formatter": {
|
|
||||||
"enabled": true,
|
|
||||||
"indentStyle": "tab"
|
|
||||||
},
|
|
||||||
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
||||||
"linter": {
|
|
||||||
"enabled": true,
|
|
||||||
"rules": {
|
|
||||||
"recommended": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"javascript": {
|
|
||||||
"formatter": {
|
|
||||||
"quoteStyle": "double"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
|
||||||
"style": "new-york",
|
|
||||||
"rsc": false,
|
|
||||||
"tsx": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "",
|
|
||||||
"css": "src/styles.css",
|
|
||||||
"baseColor": "zinc",
|
|
||||||
"cssVariables": true,
|
|
||||||
"prefix": ""
|
|
||||||
},
|
|
||||||
"aliases": {
|
|
||||||
"components": "#/components",
|
|
||||||
"utils": "#/lib/utils",
|
|
||||||
"ui": "#/components/ui",
|
|
||||||
"lib": "#/lib",
|
|
||||||
"hooks": "#/hooks"
|
|
||||||
},
|
|
||||||
"iconLibrary": "lucide"
|
|
||||||
}
|
|
||||||
5771
frontend/package-lock.json
generated
5771
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "frontend",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"imports": {
|
|
||||||
"#/*": "./src/*"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite dev --port 3000",
|
|
||||||
"build": "vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"test": "vitest run",
|
|
||||||
"format": "biome format",
|
|
||||||
"lint": "biome lint",
|
|
||||||
"check": "biome check"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
|
||||||
"@tanstack/react-devtools": "^0.7.0",
|
|
||||||
"@tanstack/react-query": "^5.66.5",
|
|
||||||
"@tanstack/react-query-devtools": "^5.84.2",
|
|
||||||
"@tanstack/react-router": "^1.132.0",
|
|
||||||
"@tanstack/react-router-devtools": "^1.132.0",
|
|
||||||
"@tanstack/react-router-ssr-query": "^1.131.7",
|
|
||||||
"@tanstack/react-start": "^1.132.0",
|
|
||||||
"@tanstack/router-plugin": "^1.132.0",
|
|
||||||
"better-auth": "^1.4.12",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"lucide-react": "^0.561.0",
|
|
||||||
"react": "^19.2.0",
|
|
||||||
"react-dom": "^19.2.0",
|
|
||||||
"tailwind-merge": "^3.0.2",
|
|
||||||
"tailwindcss": "^4.1.18",
|
|
||||||
"tw-animate-css": "^1.3.6"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@biomejs/biome": "2.2.4",
|
|
||||||
"@tanstack/devtools-vite": "^0.3.11",
|
|
||||||
"@testing-library/dom": "^10.4.0",
|
|
||||||
"@testing-library/react": "^16.2.0",
|
|
||||||
"@types/node": "^22.10.2",
|
|
||||||
"@types/react": "^19.2.0",
|
|
||||||
"@types/react-dom": "^19.2.0",
|
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
|
||||||
"jsdom": "^27.0.0",
|
|
||||||
"typescript": "^5.7.2",
|
|
||||||
"vite": "^7.1.7",
|
|
||||||
"vite-tsconfig-paths": "^5.1.4",
|
|
||||||
"vitest": "^3.0.5"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 9.4 KiB |
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"short_name": "TanStack App",
|
|
||||||
"name": "Create TanStack App Sample",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "favicon.ico",
|
|
||||||
"sizes": "64x64 32x32 24x24 16x16",
|
|
||||||
"type": "image/x-icon"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo192.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "192x192"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "logo512.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"sizes": "512x512"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"start_url": ".",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#000000",
|
|
||||||
"background_color": "#ffffff"
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# https://www.robotstxt.org/robotstxt.html
|
|
||||||
User-agent: *
|
|
||||||
Disallow:
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 259 KiB |
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 15 KiB |
@@ -1,72 +0,0 @@
|
|||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
import { Home, Menu, X } from "lucide-react";
|
|
||||||
|
|
||||||
import { useState } from "react";
|
|
||||||
import BetterAuthHeader from "../integrations/better-auth/header-user.tsx";
|
|
||||||
|
|
||||||
export default function Header() {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<header className="p-4 flex items-center bg-gray-800 text-white shadow-lg">
|
|
||||||
<button
|
|
||||||
onClick={() => setIsOpen(true)}
|
|
||||||
className="p-2 hover:bg-gray-700 rounded-lg transition-colors"
|
|
||||||
aria-label="Open menu"
|
|
||||||
>
|
|
||||||
<Menu size={24} />
|
|
||||||
</button>
|
|
||||||
<h1 className="ml-4 text-xl font-semibold">
|
|
||||||
<Link to="/">
|
|
||||||
<img
|
|
||||||
src="/tanstack-word-logo-white.svg"
|
|
||||||
alt="TanStack Logo"
|
|
||||||
className="h-10"
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<aside
|
|
||||||
className={`fixed top-0 left-0 h-full w-80 bg-gray-900 text-white shadow-2xl z-50 transform transition-transform duration-300 ease-in-out flex flex-col ${
|
|
||||||
isOpen ? "translate-x-0" : "-translate-x-full"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between p-4 border-b border-gray-700">
|
|
||||||
<h2 className="text-xl font-bold">Navigation</h2>
|
|
||||||
<button
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
className="p-2 hover:bg-gray-800 rounded-lg transition-colors"
|
|
||||||
aria-label="Close menu"
|
|
||||||
>
|
|
||||||
<X size={24} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav className="flex-1 p-4 overflow-y-auto">
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
className="flex items-center gap-3 p-3 rounded-lg hover:bg-gray-800 transition-colors mb-2"
|
|
||||||
activeProps={{
|
|
||||||
className:
|
|
||||||
"flex items-center gap-3 p-3 rounded-lg bg-cyan-600 hover:bg-cyan-700 transition-colors mb-2",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Home size={20} />
|
|
||||||
<span className="font-medium">Home</span>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{/* Demo Links Start */}
|
|
||||||
|
|
||||||
{/* Demo Links End */}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<div className="p-4 border-t border-gray-700 bg-gray-800 flex flex-col gap-2">
|
|
||||||
<BetterAuthHeader />
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { authClient } from '#/lib/auth-client'
|
|
||||||
import { Link } from '@tanstack/react-router'
|
|
||||||
|
|
||||||
export default function BetterAuthHeader() {
|
|
||||||
const { data: session, isPending } = authClient.useSession()
|
|
||||||
|
|
||||||
if (isPending) {
|
|
||||||
return (
|
|
||||||
<div className="h-8 w-8 bg-neutral-100 dark:bg-neutral-800 animate-pulse" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session?.user) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
{session.user.image ? (
|
|
||||||
<img src={session.user.image} alt="" className="h-8 w-8" />
|
|
||||||
) : (
|
|
||||||
<div className="h-8 w-8 bg-neutral-100 dark:bg-neutral-800 flex items-center justify-center">
|
|
||||||
<span className="text-xs font-medium text-neutral-600 dark:text-neutral-400">
|
|
||||||
{session.user.name?.charAt(0).toUpperCase() || 'U'}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
void authClient.signOut()
|
|
||||||
}}
|
|
||||||
className="flex-1 h-9 px-4 text-sm font-medium bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-50 border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors"
|
|
||||||
>
|
|
||||||
Sign out
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
to="/demo/better-auth"
|
|
||||||
className="h-9 px-4 text-sm font-medium bg-white dark:bg-neutral-900 text-neutral-900 dark:text-neutral-50 border border-neutral-300 dark:border-neutral-700 hover:bg-neutral-50 dark:hover:bg-neutral-800 transition-colors inline-flex items-center"
|
|
||||||
>
|
|
||||||
Sign in
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'Tanstack Query',
|
|
||||||
render: <ReactQueryDevtoolsPanel />,
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { ReactNode } from 'react'
|
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
||||||
|
|
||||||
let context:
|
|
||||||
| {
|
|
||||||
queryClient: QueryClient
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
export function getContext() {
|
|
||||||
if (context) {
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
const queryClient = new QueryClient()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
queryClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TanStackQueryProvider({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactNode
|
|
||||||
}) {
|
|
||||||
const { queryClient } = getContext()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { createAuthClient } from 'better-auth/react'
|
|
||||||
|
|
||||||
export const authClient = createAuthClient()
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { betterAuth } from 'better-auth'
|
|
||||||
import { tanstackStartCookies } from 'better-auth/tanstack-start'
|
|
||||||
|
|
||||||
export const auth = betterAuth({
|
|
||||||
emailAndPassword: {
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
plugins: [tanstackStartCookies()],
|
|
||||||
})
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import type { ClassValue } from 'clsx'
|
|
||||||
import { clsx } from 'clsx'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
|
|
||||||
// @ts-nocheck
|
|
||||||
|
|
||||||
// noinspection JSUnusedGlobalSymbols
|
|
||||||
|
|
||||||
// This file was automatically generated by TanStack Router.
|
|
||||||
// You should NOT make any changes in this file as it will be overwritten.
|
|
||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
|
||||||
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth/$'
|
|
||||||
|
|
||||||
const IndexRoute = IndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
|
||||||
id: '/api/auth/$',
|
|
||||||
path: '/api/auth/$',
|
|
||||||
getParentRoute: () => rootRouteImport,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesByTo {
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
||||||
}
|
|
||||||
export interface FileRoutesById {
|
|
||||||
__root__: typeof rootRouteImport
|
|
||||||
'/': typeof IndexRoute
|
|
||||||
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
||||||
}
|
|
||||||
export interface FileRouteTypes {
|
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
|
||||||
fullPaths: '/' | '/api/auth/$'
|
|
||||||
fileRoutesByTo: FileRoutesByTo
|
|
||||||
to: '/' | '/api/auth/$'
|
|
||||||
id: '__root__' | '/' | '/api/auth/$'
|
|
||||||
fileRoutesById: FileRoutesById
|
|
||||||
}
|
|
||||||
export interface RootRouteChildren {
|
|
||||||
IndexRoute: typeof IndexRoute
|
|
||||||
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
|
||||||
interface FileRoutesByPath {
|
|
||||||
'/': {
|
|
||||||
id: '/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/'
|
|
||||||
preLoaderRoute: typeof IndexRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
'/api/auth/$': {
|
|
||||||
id: '/api/auth/$'
|
|
||||||
path: '/api/auth/$'
|
|
||||||
fullPath: '/api/auth/$'
|
|
||||||
preLoaderRoute: typeof ApiAuthSplatRouteImport
|
|
||||||
parentRoute: typeof rootRouteImport
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
|
||||||
IndexRoute: IndexRoute,
|
|
||||||
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
|
||||||
}
|
|
||||||
export const routeTree = rootRouteImport
|
|
||||||
._addFileChildren(rootRouteChildren)
|
|
||||||
._addFileTypes<FileRouteTypes>()
|
|
||||||
|
|
||||||
import type { getRouter } from './router.tsx'
|
|
||||||
import type { createStart } from '@tanstack/react-start'
|
|
||||||
declare module '@tanstack/react-start' {
|
|
||||||
interface Register {
|
|
||||||
ssr: true
|
|
||||||
router: Awaited<ReturnType<typeof getRouter>>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
|
||||||
import { getContext } from "./integrations/tanstack-query/root-provider";
|
|
||||||
import { routeTree } from "./routeTree.gen.ts";
|
|
||||||
|
|
||||||
export function getRouter() {
|
|
||||||
const router = createTanStackRouter({
|
|
||||||
routeTree,
|
|
||||||
|
|
||||||
context: getContext(),
|
|
||||||
|
|
||||||
scrollRestoration: true,
|
|
||||||
defaultPreload: "intent",
|
|
||||||
defaultPreloadStaleTime: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare module "@tanstack/react-router" {
|
|
||||||
interface Register {
|
|
||||||
router: ReturnType<typeof getRouter>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import {
|
|
||||||
HeadContent,
|
|
||||||
Scripts,
|
|
||||||
createRootRouteWithContext,
|
|
||||||
} from '@tanstack/react-router'
|
|
||||||
import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools'
|
|
||||||
import { TanStackDevtools } from '@tanstack/react-devtools'
|
|
||||||
|
|
||||||
import Header from '../components/Header'
|
|
||||||
|
|
||||||
import TanStackQueryProvider from '../integrations/tanstack-query/root-provider'
|
|
||||||
|
|
||||||
import TanStackQueryDevtools from '../integrations/tanstack-query/devtools'
|
|
||||||
|
|
||||||
import appCss from '../styles.css?url'
|
|
||||||
|
|
||||||
import type { QueryClient } from '@tanstack/react-query'
|
|
||||||
|
|
||||||
interface MyRouterContext {
|
|
||||||
queryClient: QueryClient
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Route = createRootRouteWithContext<MyRouterContext>()({
|
|
||||||
head: () => ({
|
|
||||||
meta: [
|
|
||||||
{
|
|
||||||
charSet: 'utf-8',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'viewport',
|
|
||||||
content: 'width=device-width, initial-scale=1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'TanStack Start Starter',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
rel: 'stylesheet',
|
|
||||||
href: appCss,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
shellComponent: RootDocument,
|
|
||||||
})
|
|
||||||
|
|
||||||
function RootDocument({ children }: { children: React.ReactNode }) {
|
|
||||||
return (
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<HeadContent />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<TanStackQueryProvider>
|
|
||||||
<Header />
|
|
||||||
{children}
|
|
||||||
<TanStackDevtools
|
|
||||||
config={{
|
|
||||||
position: 'bottom-right',
|
|
||||||
}}
|
|
||||||
plugins={[
|
|
||||||
{
|
|
||||||
name: 'Tanstack Router',
|
|
||||||
render: <TanStackRouterDevtoolsPanel />,
|
|
||||||
},
|
|
||||||
TanStackQueryDevtools,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</TanStackQueryProvider>
|
|
||||||
<Scripts />
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router'
|
|
||||||
import { auth } from '#/lib/auth'
|
|
||||||
|
|
||||||
export const Route = createFileRoute('/api/auth/$')({
|
|
||||||
server: {
|
|
||||||
handlers: {
|
|
||||||
GET: ({ request }) => auth.handler(request),
|
|
||||||
POST: ({ request }) => auth.handler(request),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import {
|
|
||||||
Route as RouteIcon,
|
|
||||||
Server,
|
|
||||||
Shield,
|
|
||||||
Sparkles,
|
|
||||||
Waves,
|
|
||||||
Zap,
|
|
||||||
} from "lucide-react";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({ component: App });
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const features = [
|
|
||||||
{
|
|
||||||
icon: <Zap className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Powerful Server Functions",
|
|
||||||
description:
|
|
||||||
"Write server-side code that seamlessly integrates with your client components. Type-safe, secure, and simple.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Server className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Flexible Server Side Rendering",
|
|
||||||
description:
|
|
||||||
"Full-document SSR, streaming, and progressive enhancement out of the box. Control exactly what renders where.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <RouteIcon className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "API Routes",
|
|
||||||
description:
|
|
||||||
"Build type-safe API endpoints alongside your application. No separate backend needed.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Shield className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Strongly Typed Everything",
|
|
||||||
description:
|
|
||||||
"End-to-end type safety from server to client. Catch errors before they reach production.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Waves className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Full Streaming Support",
|
|
||||||
description:
|
|
||||||
"Stream data from server to client progressively. Perfect for AI applications and real-time updates.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: <Sparkles className="w-12 h-12 text-cyan-400" />,
|
|
||||||
title: "Next Generation Ready",
|
|
||||||
description:
|
|
||||||
"Built from the ground up for modern web applications. Deploy anywhere JavaScript runs.",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gradient-to-b from-slate-900 via-slate-800 to-slate-900">
|
|
||||||
<h1>Just a place for a guy to hang out</h1>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
@import 'tailwindcss';
|
|
||||||
|
|
||||||
@import 'tw-animate-css';
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
body {
|
|
||||||
@apply m-0;
|
|
||||||
font-family:
|
|
||||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu',
|
|
||||||
'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family:
|
|
||||||
source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--primary: oklch(0.21 0.006 285.885);
|
|
||||||
--primary-foreground: oklch(0.985 0 0);
|
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--muted: oklch(0.967 0.001 286.375);
|
|
||||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
||||||
--accent: oklch(0.967 0.001 286.375);
|
|
||||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--destructive-foreground: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.92 0.004 286.32);
|
|
||||||
--input: oklch(0.92 0.004 286.32);
|
|
||||||
--ring: oklch(0.871 0.006 286.286);
|
|
||||||
--chart-1: oklch(0.646 0.222 41.116);
|
|
||||||
--chart-2: oklch(0.6 0.118 184.704);
|
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
||||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
||||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
||||||
--sidebar-ring: oklch(0.871 0.006 286.286);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.141 0.005 285.823);
|
|
||||||
--foreground: oklch(0.985 0 0);
|
|
||||||
--card: oklch(0.141 0.005 285.823);
|
|
||||||
--card-foreground: oklch(0.985 0 0);
|
|
||||||
--popover: oklch(0.141 0.005 285.823);
|
|
||||||
--popover-foreground: oklch(0.985 0 0);
|
|
||||||
--primary: oklch(0.985 0 0);
|
|
||||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.274 0.006 286.033);
|
|
||||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
||||||
--accent: oklch(0.274 0.006 286.033);
|
|
||||||
--accent-foreground: oklch(0.985 0 0);
|
|
||||||
--destructive: oklch(0.396 0.141 25.723);
|
|
||||||
--destructive-foreground: oklch(0.637 0.237 25.331);
|
|
||||||
--border: oklch(0.274 0.006 286.033);
|
|
||||||
--input: oklch(0.274 0.006 286.033);
|
|
||||||
--ring: oklch(0.442 0.017 285.786);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.21 0.006 285.885);
|
|
||||||
--sidebar-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
||||||
--sidebar-border: oklch(0.274 0.006 286.033);
|
|
||||||
--sidebar-ring: oklch(0.442 0.017 285.786);
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-destructive-foreground: var(--destructive-foreground);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) + 4px);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
{
|
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"module": "ESNext",
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
},
|
|
||||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
||||||
"types": ["vite/client"],
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"verbatimModuleSyntax": true,
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { defineConfig } from 'vite'
|
|
||||||
import { devtools } from '@tanstack/devtools-vite'
|
|
||||||
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
||||||
|
|
||||||
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
||||||
|
|
||||||
import viteReact from '@vitejs/plugin-react'
|
|
||||||
import tailwindcss from '@tailwindcss/vite'
|
|
||||||
|
|
||||||
const config = defineConfig({
|
|
||||||
plugins: [
|
|
||||||
devtools(),
|
|
||||||
tsconfigPaths({ projects: ['./tsconfig.json'] }),
|
|
||||||
tailwindcss(),
|
|
||||||
tanstackStart(),
|
|
||||||
viteReact(),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
export default config
|
|
||||||
Reference in New Issue
Block a user