authentication fix
This commit is contained in:
@@ -1,3 +1,25 @@
|
|||||||
|
# 🚀 TasGrid Server Management
|
||||||
|
|
||||||
|
This project uses **PM2** (via Bun) to stay online 24/7. The app runs on port `4000` and is managed by the scripts in `package.json`.
|
||||||
|
|
||||||
|
## 🛠 Quick Commands
|
||||||
|
|
||||||
|
| Action | Command | Description |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **Check Status** | `bun status` | See if the site is online/how much memory it's using. |
|
||||||
|
| **Update Site** | `bun deploy` | Pulls latest code, builds, and restarts the server. |
|
||||||
|
| **Stop Server** | `bun stop` | Shuts down the site (use for maintenance). |
|
||||||
|
| **View Logs** | `bun logs` | See real-time errors or console output (Ctrl+C to exit). |
|
||||||
|
|
||||||
|
## 🔄 Deployment Workflow
|
||||||
|
To push new changes live, simply run:
|
||||||
|
```bash
|
||||||
|
bun deploy
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
+8
-2
@@ -4,9 +4,15 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --host 0.0.0.0 --port 4000",
|
"dev": "vite --host 0.0.0.0 --port 4001",
|
||||||
"build": "tsc -b && vite build",
|
"build": "tsc -b && vite build",
|
||||||
"preview": "vite preview"
|
"serve": "vite preview --host 0.0.0.0 --port 4000",
|
||||||
|
"---PM2 COMMANDS---": "------------------",
|
||||||
|
"status": "bun $(command -v pm2) status",
|
||||||
|
"logs": "bun $(command -v pm2) logs tasgrid",
|
||||||
|
"stop": "bun $(command -v pm2) stop tasgrid",
|
||||||
|
"start-prod": "bun $(command -v pm2) start \"bun run serve\" --name \"tasgrid\"",
|
||||||
|
"deploy": "git pull && bun run build && bun $(command -v pm2) restart tasgrid"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@kobalte/core": "^0.13.11",
|
"@kobalte/core": "^0.13.11",
|
||||||
|
|||||||
-27
@@ -1,27 +0,0 @@
|
|||||||
#root {
|
|
||||||
max-width: 1280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.solid:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
@@ -34,7 +34,10 @@ export const AuthCallback = (props: { onSuccess: () => void }) => {
|
|||||||
<form onSubmit={handleLogin} class="space-y-4">
|
<form onSubmit={handleLogin} class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<input
|
<input
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
|
autocomplete="email"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
value={email()}
|
value={email()}
|
||||||
onInput={(e) => setEmail(e.currentTarget.value)}
|
onInput={(e) => setEmail(e.currentTarget.value)}
|
||||||
@@ -44,7 +47,10 @@ export const AuthCallback = (props: { onSuccess: () => void }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<input
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
|
autocomplete="current-password"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
value={password()}
|
value={password()}
|
||||||
onInput={(e) => setPassword(e.currentTarget.value)}
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
||||||
|
|||||||
@@ -61,10 +61,12 @@ export const Layout: Component<LayoutProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<main class="flex-1 overflow-auto p-4 md:p-8 pt-4 pb-20 md:pb-8 scroll-smooth relative z-0">
|
{/* Main Content Area */}
|
||||||
{props.children}
|
<main class="flex-1 overflow-y-auto overflow-x-hidden p-4 sm:p-6 lg:p-8 pb-32 sm:pb-6">
|
||||||
|
<div class="max-w-5xl mx-auto w-full">
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{/* Mobile Bottom Nav */}
|
{/* Mobile Bottom Nav */}
|
||||||
<BottomNav currentView={props.currentView} setView={props.setView} />
|
<BottomNav currentView={props.currentView} setView={props.setView} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -154,6 +154,8 @@ export const QuickEntry: Component = () => {
|
|||||||
<Search class="text-muted-foreground mr-3" size={20} />
|
<Search class="text-muted-foreground mr-3" size={20} />
|
||||||
<TextField class="flex-1">
|
<TextField class="flex-1">
|
||||||
<TextFieldInput
|
<TextFieldInput
|
||||||
|
id="quick-entry-input"
|
||||||
|
name="task-title"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
value={input()}
|
value={input()}
|
||||||
onInput={(e) => setInput(e.currentTarget.value)}
|
onInput={(e) => setInput(e.currentTarget.value)}
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
{/* Header Area */}
|
{/* Header Area */}
|
||||||
<div class="px-6 pt-6 pb-2 shrink-0">
|
<div class="px-6 pt-6 pb-2 shrink-0">
|
||||||
<textarea
|
<textarea
|
||||||
|
id="task-detail-title"
|
||||||
|
name="task-title"
|
||||||
value={title()}
|
value={title()}
|
||||||
onInput={(e) => {
|
onInput={(e) => {
|
||||||
updateTitle(e.currentTarget.value);
|
updateTitle(e.currentTarget.value);
|
||||||
@@ -160,6 +162,8 @@ export const TaskDetail: Component<TaskDetailProps> = (props) => {
|
|||||||
<div class="flex items-center gap-1 text-muted-foreground group relative shrink-0 min-w-0 flex-1 sm:flex-initial">
|
<div class="flex items-center gap-1 text-muted-foreground group relative shrink-0 min-w-0 flex-1 sm:flex-initial">
|
||||||
<Calendar size={14} class="group-hover:text-primary transition-colors shrink-0" />
|
<Calendar size={14} class="group-hover:text-primary transition-colors shrink-0" />
|
||||||
<input
|
<input
|
||||||
|
id="task-detail-date"
|
||||||
|
name="due-date"
|
||||||
type="datetime-local"
|
type="datetime-local"
|
||||||
value={dateString()}
|
value={dateString()}
|
||||||
onInput={onDateInputChange}
|
onInput={onDateInputChange}
|
||||||
|
|||||||
+30
-22
@@ -3,7 +3,10 @@ import { createSignal, createEffect, createRoot } from "solid-js";
|
|||||||
import { pb, TASGRID_COLLECTION } from "@/lib/pocketbase";
|
import { pb, TASGRID_COLLECTION } from "@/lib/pocketbase";
|
||||||
import { toast } from "solid-sonner";
|
import { toast } from "solid-sonner";
|
||||||
|
|
||||||
const STORAGE_KEY = "tasgrid_data_v1";
|
const getStorageKey = () => {
|
||||||
|
const userId = pb.authStore.model?.id;
|
||||||
|
return userId ? `tasgrid_data_${userId}` : null;
|
||||||
|
};
|
||||||
|
|
||||||
export const [now, setNow] = createSignal(Date.now());
|
export const [now, setNow] = createSignal(Date.now());
|
||||||
export const [activeTaskId, setActiveTaskId] = createSignal<string | null>(null);
|
export const [activeTaskId, setActiveTaskId] = createSignal<string | null>(null);
|
||||||
@@ -30,30 +33,21 @@ interface TaskStore {
|
|||||||
prefId?: string; // ID of the user_preferences record
|
prefId?: string; // ID of the user_preferences record
|
||||||
}
|
}
|
||||||
|
|
||||||
// Synchronous hydration from localStorage for "instant" first paint
|
// Initial empty state
|
||||||
const getInitialState = (): TaskStore => {
|
export const [store, setStore] = createStore<TaskStore>({
|
||||||
const saved = localStorage.getItem(STORAGE_KEY);
|
tasks: [],
|
||||||
if (saved) {
|
pWeight: 1.0,
|
||||||
try {
|
uWeight: 1.0,
|
||||||
return JSON.parse(saved);
|
matrixScaleDays: 30,
|
||||||
} catch (e) {
|
});
|
||||||
console.error("Failed to parse cached data", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
tasks: [],
|
|
||||||
pWeight: 1.0,
|
|
||||||
uWeight: 1.0,
|
|
||||||
matrixScaleDays: 30,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const [store, setStore] = createStore<TaskStore>(getInitialState());
|
|
||||||
|
|
||||||
// Auto-persist changes to localStorage (wrapped in root to avoid console warning)
|
// Auto-persist changes to localStorage (wrapped in root to avoid console warning)
|
||||||
createRoot(() => {
|
createRoot(() => {
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(store));
|
const key = getStorageKey();
|
||||||
|
if (key) {
|
||||||
|
localStorage.setItem(key, JSON.stringify(store));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -100,6 +94,19 @@ let heartbeatInterval: number | undefined;
|
|||||||
export const initStore = async () => {
|
export const initStore = async () => {
|
||||||
if (!pb.authStore.isValid) return;
|
if (!pb.authStore.isValid) return;
|
||||||
|
|
||||||
|
// 0. Synchronous hydration from localStorage for "instant" first paint
|
||||||
|
const key = getStorageKey();
|
||||||
|
if (key) {
|
||||||
|
const saved = localStorage.getItem(key);
|
||||||
|
if (saved) {
|
||||||
|
try {
|
||||||
|
setStore(JSON.parse(saved));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to parse cached data", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Heartbeat - Ensure only one interval is running
|
// Heartbeat - Ensure only one interval is running
|
||||||
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
||||||
heartbeatInterval = setInterval(() => setNow(Date.now()), 60000);
|
heartbeatInterval = setInterval(() => setNow(Date.now()), 60000);
|
||||||
@@ -127,8 +134,9 @@ export const initStore = async () => {
|
|||||||
console.warn("Failed to load preferences from user profile:", prefErr);
|
console.warn("Failed to load preferences from user profile:", prefErr);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Fetch Tasks
|
// 2. Fetch Tasks - Explicitly filtered by user and sorted
|
||||||
const records = await pb.collection(TASGRID_COLLECTION).getFullList({
|
const records = await pb.collection(TASGRID_COLLECTION).getFullList({
|
||||||
|
filter: `user = "${pb.authStore.model?.id}"`,
|
||||||
sort: '-created',
|
sort: '-created',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user