aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/components')
-rw-r--r--src/client/components/CreateCardModal.tsx6
-rw-r--r--src/client/components/CreateDeckModal.tsx6
-rw-r--r--src/client/components/CreateNoteModal.tsx8
-rw-r--r--src/client/components/CreateNoteTypeModal.tsx6
-rw-r--r--src/client/components/DeleteCardModal.tsx6
-rw-r--r--src/client/components/DeleteDeckModal.tsx6
-rw-r--r--src/client/components/DeleteNoteModal.tsx6
-rw-r--r--src/client/components/DeleteNoteTypeModal.tsx6
-rw-r--r--src/client/components/EditCardModal.tsx6
-rw-r--r--src/client/components/EditDeckModal.tsx6
-rw-r--r--src/client/components/EditNoteModal.tsx8
-rw-r--r--src/client/components/EditNoteTypeModal.tsx6
-rw-r--r--src/client/components/ImportNotesModal.tsx6
-rw-r--r--src/client/components/SyncStatusIndicator.tsx6
14 files changed, 74 insertions, 14 deletions
diff --git a/src/client/components/CreateCardModal.tsx b/src/client/components/CreateCardModal.tsx
index 3913e82..8dbaa79 100644
--- a/src/client/components/CreateCardModal.tsx
+++ b/src/client/components/CreateCardModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface CreateCardModalProps {
isOpen: boolean;
@@ -18,6 +20,7 @@ export function CreateCardModal({
const [back, setBack] = useState("");
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const resetForm = () => {
setFront("");
@@ -163,7 +166,8 @@ export function CreateCardModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !isFormValid}
+ disabled={isSubmitting || !isFormValid || !isOnline}
+ title={!isOnline ? "Reconnect to create a card" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Creating..." : "Create Card"}
diff --git a/src/client/components/CreateDeckModal.tsx b/src/client/components/CreateDeckModal.tsx
index 4541a68..34d46e7 100644
--- a/src/client/components/CreateDeckModal.tsx
+++ b/src/client/components/CreateDeckModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface CreateDeckModalProps {
isOpen: boolean;
@@ -16,6 +18,7 @@ export function CreateDeckModal({
const [description, setDescription] = useState("");
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const resetForm = () => {
setName("");
@@ -160,7 +163,8 @@ export function CreateDeckModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !name.trim()}
+ disabled={isSubmitting || !name.trim() || !isOnline}
+ title={!isOnline ? "Reconnect to create a deck" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Creating..." : "Create Deck"}
diff --git a/src/client/components/CreateNoteModal.tsx b/src/client/components/CreateNoteModal.tsx
index cc39bf6..f3809ea 100644
--- a/src/client/components/CreateNoteModal.tsx
+++ b/src/client/components/CreateNoteModal.tsx
@@ -1,7 +1,9 @@
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useAtomValue } from "jotai";
import { type FormEvent, useCallback, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface NoteField {
id: string;
@@ -49,6 +51,7 @@ export function CreateNoteModal({
const [isLoadingNoteType, setIsLoadingNoteType] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [hasLoadedNoteTypes, setHasLoadedNoteTypes] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const fetchNoteTypeDetails = useCallback(async (noteTypeId: string) => {
setIsLoadingNoteType(true);
@@ -346,7 +349,10 @@ export function CreateNoteModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !isFormValid || isLoading}
+ disabled={
+ isSubmitting || !isFormValid || isLoading || !isOnline
+ }
+ title={!isOnline ? "Reconnect to create a note" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Creating..." : "Create Note"}
diff --git a/src/client/components/CreateNoteTypeModal.tsx b/src/client/components/CreateNoteTypeModal.tsx
index 4c3b232..bbd43a1 100644
--- a/src/client/components/CreateNoteTypeModal.tsx
+++ b/src/client/components/CreateNoteTypeModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface CreateNoteTypeModalProps {
isOpen: boolean;
@@ -18,6 +20,7 @@ export function CreateNoteTypeModal({
const [isReversible, setIsReversible] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const resetForm = () => {
setName("");
@@ -197,7 +200,8 @@ export function CreateNoteTypeModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !name.trim()}
+ disabled={isSubmitting || !name.trim() || !isOnline}
+ title={!isOnline ? "Reconnect to create" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Creating..." : "Create"}
diff --git a/src/client/components/DeleteCardModal.tsx b/src/client/components/DeleteCardModal.tsx
index d9cf098..99514be 100644
--- a/src/client/components/DeleteCardModal.tsx
+++ b/src/client/components/DeleteCardModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface Card {
id: string;
@@ -23,6 +25,7 @@ export function DeleteCardModal({
}: DeleteCardModalProps) {
const [error, setError] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const handleClose = () => {
setError(null);
@@ -138,7 +141,8 @@ export function DeleteCardModal({
<button
type="button"
onClick={handleDelete}
- disabled={isDeleting}
+ disabled={isDeleting || !isOnline}
+ title={!isOnline ? "Reconnect to delete" : undefined}
className="px-4 py-2 bg-error hover:bg-error/90 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed min-w-[100px]"
>
{isDeleting ? "Deleting..." : "Delete"}
diff --git a/src/client/components/DeleteDeckModal.tsx b/src/client/components/DeleteDeckModal.tsx
index edc6093..954431e 100644
--- a/src/client/components/DeleteDeckModal.tsx
+++ b/src/client/components/DeleteDeckModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface Deck {
id: string;
@@ -21,6 +23,7 @@ export function DeleteDeckModal({
}: DeleteDeckModalProps) {
const [error, setError] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const handleClose = () => {
setError(null);
@@ -129,7 +132,8 @@ export function DeleteDeckModal({
<button
type="button"
onClick={handleDelete}
- disabled={isDeleting}
+ disabled={isDeleting || !isOnline}
+ title={!isOnline ? "Reconnect to delete" : undefined}
className="px-4 py-2 bg-error hover:bg-error/90 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed min-w-[100px]"
>
{isDeleting ? "Deleting..." : "Delete"}
diff --git a/src/client/components/DeleteNoteModal.tsx b/src/client/components/DeleteNoteModal.tsx
index 5d81fdc..3ed22ec 100644
--- a/src/client/components/DeleteNoteModal.tsx
+++ b/src/client/components/DeleteNoteModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface DeleteNoteModalProps {
isOpen: boolean;
@@ -18,6 +20,7 @@ export function DeleteNoteModal({
}: DeleteNoteModalProps) {
const [error, setError] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const handleClose = () => {
setError(null);
@@ -127,7 +130,8 @@ export function DeleteNoteModal({
<button
type="button"
onClick={handleDelete}
- disabled={isDeleting}
+ disabled={isDeleting || !isOnline}
+ title={!isOnline ? "Reconnect to delete" : undefined}
className="px-4 py-2 bg-error hover:bg-error/90 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed min-w-[100px]"
>
{isDeleting ? "Deleting..." : "Delete"}
diff --git a/src/client/components/DeleteNoteTypeModal.tsx b/src/client/components/DeleteNoteTypeModal.tsx
index db93482..2fbf808 100644
--- a/src/client/components/DeleteNoteTypeModal.tsx
+++ b/src/client/components/DeleteNoteTypeModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface NoteType {
id: string;
@@ -21,6 +23,7 @@ export function DeleteNoteTypeModal({
}: DeleteNoteTypeModalProps) {
const [error, setError] = useState<string | null>(null);
const [isDeleting, setIsDeleting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const handleClose = () => {
setError(null);
@@ -129,7 +132,8 @@ export function DeleteNoteTypeModal({
<button
type="button"
onClick={handleDelete}
- disabled={isDeleting}
+ disabled={isDeleting || !isOnline}
+ title={!isOnline ? "Reconnect to delete" : undefined}
className="px-4 py-2 bg-error hover:bg-error/90 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed min-w-[100px]"
>
{isDeleting ? "Deleting..." : "Delete"}
diff --git a/src/client/components/EditCardModal.tsx b/src/client/components/EditCardModal.tsx
index 726a003..288bfd6 100644
--- a/src/client/components/EditCardModal.tsx
+++ b/src/client/components/EditCardModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface Card {
id: string;
@@ -26,6 +28,7 @@ export function EditCardModal({
const [back, setBack] = useState("");
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
// Sync form state when card changes
useEffect(() => {
@@ -164,7 +167,8 @@ export function EditCardModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !isFormValid}
+ disabled={isSubmitting || !isFormValid || !isOnline}
+ title={!isOnline ? "Reconnect to save changes" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Saving..." : "Save Changes"}
diff --git a/src/client/components/EditDeckModal.tsx b/src/client/components/EditDeckModal.tsx
index 9a79de8..e9c2b7b 100644
--- a/src/client/components/EditDeckModal.tsx
+++ b/src/client/components/EditDeckModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useCallback, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface Deck {
id: string;
@@ -35,6 +37,7 @@ export function EditDeckModal({
const [isLoadingNoteTypes, setIsLoadingNoteTypes] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const fetchNoteTypes = useCallback(async () => {
setIsLoadingNoteTypes(true);
@@ -216,7 +219,8 @@ export function EditDeckModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !name.trim()}
+ disabled={isSubmitting || !name.trim() || !isOnline}
+ title={!isOnline ? "Reconnect to save changes" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Saving..." : "Save Changes"}
diff --git a/src/client/components/EditNoteModal.tsx b/src/client/components/EditNoteModal.tsx
index ac22332..cd2c58c 100644
--- a/src/client/components/EditNoteModal.tsx
+++ b/src/client/components/EditNoteModal.tsx
@@ -1,7 +1,9 @@
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useAtomValue } from "jotai";
import { type FormEvent, useCallback, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface NoteField {
id: string;
@@ -54,6 +56,7 @@ export function EditNoteModal({
const [isLoadingNote, setIsLoadingNote] = useState(false);
const [isLoadingNoteType, setIsLoadingNoteType] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
const fetchNoteTypeDetails = useCallback(async (noteTypeId: string) => {
setIsLoadingNoteType(true);
@@ -297,7 +300,10 @@ export function EditNoteModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !isFormValid || isLoading}
+ disabled={
+ isSubmitting || !isFormValid || isLoading || !isOnline
+ }
+ title={!isOnline ? "Reconnect to save changes" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Saving..." : "Save Changes"}
diff --git a/src/client/components/EditNoteTypeModal.tsx b/src/client/components/EditNoteTypeModal.tsx
index 27ef5d8..5916ff0 100644
--- a/src/client/components/EditNoteTypeModal.tsx
+++ b/src/client/components/EditNoteTypeModal.tsx
@@ -1,5 +1,7 @@
+import { useAtomValue } from "jotai";
import { type FormEvent, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
interface NoteType {
id: string;
@@ -28,6 +30,7 @@ export function EditNoteTypeModal({
const [isReversible, setIsReversible] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const isOnline = useAtomValue(isOnlineAtom);
// Sync form state when noteType changes
useEffect(() => {
@@ -208,7 +211,8 @@ export function EditNoteTypeModal({
</button>
<button
type="submit"
- disabled={isSubmitting || !name.trim()}
+ disabled={isSubmitting || !name.trim() || !isOnline}
+ title={!isOnline ? "Reconnect to save changes" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isSubmitting ? "Saving..." : "Save Changes"}
diff --git a/src/client/components/ImportNotesModal.tsx b/src/client/components/ImportNotesModal.tsx
index d3a2c0c..a38ac8f 100644
--- a/src/client/components/ImportNotesModal.tsx
+++ b/src/client/components/ImportNotesModal.tsx
@@ -5,8 +5,10 @@ import {
faSpinner,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { useAtomValue } from "jotai";
import { type ChangeEvent, useCallback, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
+import { isOnlineAtom } from "../atoms";
import { parseCSV } from "../utils/csvParser";
interface NoteField {
@@ -64,6 +66,7 @@ export function ImportNotesModal({
}: ImportNotesModalProps) {
const [phase, setPhase] = useState<ImportPhase>("upload");
const [error, setError] = useState<string | null>(null);
+ const isOnline = useAtomValue(isOnlineAtom);
const [noteTypes, setNoteTypes] = useState<NoteType[]>([]);
const [validatedRows, setValidatedRows] = useState<ValidatedRow[]>([]);
const [validationErrors, setValidationErrors] = useState<ValidationError[]>(
@@ -490,7 +493,8 @@ export function ImportNotesModal({
<button
type="button"
onClick={handleImport}
- disabled={validatedRows.length === 0}
+ disabled={validatedRows.length === 0 || !isOnline}
+ title={!isOnline ? "Reconnect to import notes" : undefined}
className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
Import {validatedRows.length} Note(s)
diff --git a/src/client/components/SyncStatusIndicator.tsx b/src/client/components/SyncStatusIndicator.tsx
index 4bb3ff5..c517b76 100644
--- a/src/client/components/SyncStatusIndicator.tsx
+++ b/src/client/components/SyncStatusIndicator.tsx
@@ -101,11 +101,15 @@ export function SyncStatusIndicator() {
);
};
+ const titleText = !isOnline
+ ? "Showing cached data — changes will sync when you're back online"
+ : lastError || undefined;
+
return (
<div
data-testid="sync-status-indicator"
className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium transition-colors ${getStatusStyles()}`}
- title={lastError || undefined}
+ title={titleText}
>
{getStatusIcon()}
<span>{getStatusText()}</span>