● Zion Boggan
repos/Drophawk/frontend/app/settings/page.tsx
zionboggan.com ↗
127 lines · tsx
History for this file →
1
"use client";
2
 
3
import { useEffect, useState } from "react";
4
import { BellRing, Moon, Smartphone } from "lucide-react";
5
import { toast } from "sonner";
6
import { Card, CardBody, CardHeader, CardTitle } from "@/components/ui/card";
7
import { Button } from "@/components/ui/button";
8
import { Input, Label } from "@/components/ui/input";
9
import { Switch } from "@/components/ui/switch";
10
import { api } from "@/lib/api";
11
 
12
interface QuietHours {
13
  enabled: boolean;
14
  start: string;
15
  end: string;
16
}
17
 
18
const KEY = "drophawk:quiet-hours";
19
 
20
export default function SettingsPage() {
21
  const [topic] = useState(process.env.NEXT_PUBLIC_NTFY_TOPIC ?? "drophawk-drops");
22
  const [testing, setTesting] = useState(false);
23
  const [quiet, setQuiet] = useState<QuietHours>({ enabled: false, start: "23:00", end: "08:00" });
24
 
25
  useEffect(() => {
26
    try {
27
      const raw = localStorage.getItem(KEY);
28
      if (raw) setQuiet(JSON.parse(raw) as QuietHours);
29
    } catch {
30
    }
31
  }, []);
32
 
33
  const persist = (next: QuietHours) => {
34
    setQuiet(next);
35
    try {
36
      localStorage.setItem(KEY, JSON.stringify(next));
37
    } catch {
38
    }
39
  };
40
 
41
  const sendTest = async () => {
42
    setTesting(true);
43
    try {
44
      const res = await api.testNotification();
45
      toast.success(res.sent ? `Test push sent to ${res.topic}` : "Test queued");
46
    } catch (e) {
47
      toast.error(e instanceof Error ? e.message : "Could not send test push");
48
    } finally {
49
      setTesting(false);
50
    }
51
  };
52
 
53
  return (
54
    <div className="mx-auto max-w-2xl space-y-5">
55
      <div>
56
        <h2 className="text-lg font-semibold tracking-[var(--tracking-tight)]">Settings</h2>
57
        <p className="mt-0.5 text-sm text-[var(--color-fg-tertiary)]">
58
          Push delivery and quiet hours for this device.
59
        </p>
60
      </div>
61
 
62
      <Card>
63
        <CardHeader>
64
          <CardTitle className="flex items-center gap-2">
65
            <Smartphone className="h-4 w-4 text-[var(--color-accent-teal)]" aria-hidden />
66
            Push delivery
67
          </CardTitle>
68
        </CardHeader>
69
        <CardBody className="space-y-4">
70
          <div className="space-y-1.5">
71
            <Label htmlFor="ntfy-topic">ntfy topic</Label>
72
            <Input id="ntfy-topic" value={topic} readOnly aria-readonly className="font-mono" />
73
            <p className="text-xs text-[var(--color-fg-tertiary)]">
74
              Subscribe to this topic in the ntfy app on your phone to receive instant pushes.
75
            </p>
76
          </div>
77
          <Button onClick={sendTest} loading={testing} variant="secondary">
78
            <BellRing className="h-4 w-4" aria-hidden />
79
            Send test push
80
          </Button>
81
        </CardBody>
82
      </Card>
83
 
84
      <Card>
85
        <CardHeader>
86
          <CardTitle className="flex items-center gap-2">
87
            <Moon className="h-4 w-4 text-[var(--color-accent-violet)]" aria-hidden />
88
            Quiet hours
89
          </CardTitle>
90
          <Switch
91
            checked={quiet.enabled}
92
            onCheckedChange={(enabled) => persist({ ...quiet, enabled })}
93
            aria-label="Toggle quiet hours"
94
          />
95
        </CardHeader>
96
        <CardBody>
97
          <div className="grid grid-cols-2 gap-3">
98
            <div className="space-y-1.5">
99
              <Label htmlFor="q-start">From</Label>
100
              <Input
101
                id="q-start"
102
                type="time"
103
                value={quiet.start}
104
                disabled={!quiet.enabled}
105
                onChange={(e) => persist({ ...quiet, start: e.target.value })}
106
              />
107
            </div>
108
            <div className="space-y-1.5">
109
              <Label htmlFor="q-end">Until</Label>
110
              <Input
111
                id="q-end"
112
                type="time"
113
                value={quiet.end}
114
                disabled={!quiet.enabled}
115
                onChange={(e) => persist({ ...quiet, end: e.target.value })}
116
              />
117
            </div>
118
          </div>
119
          <p className="mt-3 text-xs text-[var(--color-fg-tertiary)]">
120
            During quiet hours, only <span className="text-[var(--color-accent-red)]">critical</span> watchlist
121
            matches break through. Everything else waits for morning.
122
          </p>
123
        </CardBody>
124
      </Card>
125
    </div>
126
  );
127
}