Skip to content

Commit 04dd36e

Browse files
committed
pretty fixes + alert fix in stacked textareas
1 parent 4cff09d commit 04dd36e

File tree

6 files changed

+125
-52
lines changed

6 files changed

+125
-52
lines changed

src/App.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,17 @@ export function App() {
2626
direction="horizontal"
2727
className="h-full w-full"
2828
>
29-
<ResizablePanel defaultSize={34} minSize={20}>
29+
<ResizablePanel defaultSize={20} minSize={15}>
3030
<ToolBaseContainer>
31-
<JwtDecoderTool />
31+
<UnixTimestampTool />
3232
</ToolBaseContainer>
3333
</ResizablePanel>
3434

3535
<ResizableHandle />
3636

37-
<ResizablePanel defaultSize={33} minSize={20}>
37+
<ResizablePanel defaultSize={34} minSize={20}>
3838
<ToolBaseContainer>
39-
<UnixTimestampTool />
39+
<JwtDecoderTool />
4040
</ToolBaseContainer>
4141
</ResizablePanel>
4242

src/tools/stacked-textareas/lib/use-stacked-textareas.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,16 @@ export function useStackedTextareas() {
5757
(index: number) => {
5858
return function handleRemove() {
5959
const target = entries[index] ?? "";
60+
let confirmed = false;
6061
if (target.trim()) {
61-
window.alert("textarea contains text");
62+
confirmed = window.confirm("textarea contains text");
63+
}
64+
if (confirmed) {
65+
setEntries((previous) => {
66+
if (previous.length <= 1) return previous;
67+
return previous.filter((_, currentIndex) => currentIndex !== index);
68+
});
6269
}
63-
setEntries((previous) => {
64-
if (previous.length <= 1) return previous;
65-
return previous.filter((_, currentIndex) => currentIndex !== index);
66-
});
6770
};
6871
},
6972
[entries, setEntries]
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
export interface TimestampFormatOption {
2-
id: "date-string" | "iso" | "locale"
3-
label: string
2+
id: "date-string" | "iso" | "locale";
3+
label: string;
44
}
55

66
export interface UnixTimestampState {
7-
input: string
8-
format: TimestampFormatOption["id"]
7+
input: string;
8+
format: TimestampFormatOption["id"];
99
}
1010

1111
export const TIMESTAMP_FORMAT_OPTIONS: TimestampFormatOption[] = [
1212
{ id: "date-string", label: "date string" },
1313
{ id: "iso", label: "iso" },
1414
{ id: "locale", label: "locale" },
15-
]
15+
];
1616

1717
export const DEFAULT_UNIX_TIMESTAMP_STATE: UnixTimestampState = {
1818
input: "",
1919
format: "date-string",
20-
}
20+
};

src/tools/unix-timestamp/lib/use-unix-timestamp.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import { formatTimestamp } from "@/tools/unix-timestamp/lib/utils";
1313
export interface UnixTimestampOutput {
1414
gmt: string;
1515
local: string;
16+
prettyDate: string;
17+
prettyTime: string;
18+
prettyDisplay: string;
1619
}
1720

1821
function serializeTimestampState(state: UnixTimestampState) {
@@ -40,7 +43,16 @@ export function useUnixTimestamp() {
4043

4144
const output = useMemo<UnixTimestampOutput>(() => {
4245
const result = formatTimestamp(state.input, state.format);
43-
return { gmt: result.gmt, local: result.local };
46+
const prettyDisplay = result.isValid
47+
? `${result.prettyDate} | ${result.prettyTime}`
48+
: result.prettyDate;
49+
return {
50+
gmt: result.gmt,
51+
local: result.local,
52+
prettyDate: result.prettyDate,
53+
prettyTime: result.prettyTime,
54+
prettyDisplay,
55+
};
4456
}, [state]);
4557

4658
function handleInputChange(event: ChangeEvent<HTMLInputElement>) {
Lines changed: 59 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,88 @@
1-
import type { TimestampFormatOption } from "@/tools/unix-timestamp/lib/constants"
1+
import type { TimestampFormatOption } from "@/tools/unix-timestamp/lib/constants";
22

33
export interface TimestampResult {
4-
gmt: string
5-
local: string
6-
isValid: boolean
4+
gmt: string;
5+
local: string;
6+
prettyDate: string;
7+
prettyTime: string;
8+
isValid: boolean;
79
}
810

911
function toDate(input: string) {
10-
const trimmed = input.trim()
11-
if (!trimmed) return null
12-
const numeric = Number(trimmed)
13-
if (!Number.isFinite(numeric)) return null
14-
const isSeconds = trimmed.length <= 10
15-
const millis = isSeconds ? numeric * 1000 : numeric
16-
const date = new Date(millis)
17-
if (Number.isNaN(date.getTime())) return null
18-
return date
12+
const trimmed = input.trim();
13+
if (!trimmed) return null;
14+
const numeric = Number(trimmed);
15+
if (!Number.isFinite(numeric)) return null;
16+
const isSeconds = trimmed.length <= 10;
17+
const millis = isSeconds ? numeric * 1000 : numeric;
18+
const date = new Date(millis);
19+
if (Number.isNaN(date.getTime())) return null;
20+
return date;
21+
}
22+
23+
export interface PrettyDateParts {
24+
dateLabel: string;
25+
timeLabel: string;
26+
}
27+
28+
export function formatPrettyDateParts(date: Date): PrettyDateParts {
29+
const dateLabel = date.toLocaleDateString("en-US", {
30+
weekday: "short",
31+
month: "short",
32+
day: "2-digit",
33+
year: "numeric",
34+
});
35+
const timeLabel = date.toLocaleTimeString("en-US", {
36+
hour: "2-digit",
37+
minute: "2-digit",
38+
});
39+
return { dateLabel, timeLabel };
1940
}
2041

2142
function formatGmt(date: Date, format: TimestampFormatOption["id"]) {
22-
if (format === "iso") return date.toISOString()
43+
if (format === "iso") return date.toISOString();
2344
if (format === "locale") {
24-
return date.toLocaleString("en-US", { timeZone: "UTC" })
45+
return date.toLocaleString("en-US", { timeZone: "UTC" });
2546
}
26-
return date.toUTCString()
47+
return date.toUTCString();
2748
}
2849

2950
function formatLocal(date: Date, format: TimestampFormatOption["id"]) {
3051
if (format === "iso") {
31-
const offsetMinutes = date.getTimezoneOffset()
32-
const localDate = new Date(date.getTime() - offsetMinutes * 60000)
33-
const iso = localDate.toISOString().replace("Z", "")
34-
const sign = offsetMinutes <= 0 ? "+" : "-"
35-
const abs = Math.abs(offsetMinutes)
36-
const hours = String(Math.floor(abs / 60)).padStart(2, "0")
37-
const minutes = String(abs % 60).padStart(2, "0")
38-
return `${iso}${sign}${hours}${minutes}`
52+
const offsetMinutes = date.getTimezoneOffset();
53+
const localDate = new Date(date.getTime() - offsetMinutes * 60000);
54+
const iso = localDate.toISOString().replace("Z", "");
55+
const sign = offsetMinutes <= 0 ? "+" : "-";
56+
const abs = Math.abs(offsetMinutes);
57+
const hours = String(Math.floor(abs / 60)).padStart(2, "0");
58+
const minutes = String(abs % 60).padStart(2, "0");
59+
return `${iso}${sign}${hours}${minutes}`;
3960
}
40-
if (format === "locale") return date.toLocaleString()
41-
return date.toString()
61+
if (format === "locale") return date.toLocaleString();
62+
return date.toString();
4263
}
4364

4465
export function formatTimestamp(
4566
input: string,
4667
format: TimestampFormatOption["id"]
4768
): TimestampResult {
48-
const date = toDate(input)
69+
const date = toDate(input);
4970
if (!date) {
50-
return { gmt: "", local: "", isValid: false }
71+
return {
72+
gmt: "invalid",
73+
local: "invalid",
74+
prettyDate: "invalid",
75+
prettyTime: "",
76+
isValid: false,
77+
};
5178
}
79+
const pretty = formatPrettyDateParts(date);
5280

5381
return {
5482
gmt: formatGmt(date, format),
5583
local: formatLocal(date, format),
84+
prettyDate: pretty.dateLabel,
85+
prettyTime: pretty.timeLabel,
5686
isValid: true,
57-
}
87+
};
5888
}

src/tools/unix-timestamp/unix-timestamp.tsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,13 @@ export function UnixTimestampTool() {
4545
</Select>
4646
</div>
4747
<div className="flex flex-1 flex-col gap-0">
48-
<TimestampOutputRow
49-
label="gmt"
50-
value={output.gmt}
51-
className="border-b border-border"
48+
<TimestampOutputRow label="gmt tz" value={output.gmt} />
49+
<TimestampOutputRow label="local tz" value={output.local} />
50+
<PrettyDateOutputRow
51+
date={output.prettyDate}
52+
time={output.prettyTime}
53+
displayValue={output.prettyDisplay}
5254
/>
53-
<TimestampOutputRow label="your time zone" value={output.local} />
5455
</div>
5556
</div>
5657
);
@@ -62,6 +63,33 @@ interface TimestampOutputRowProps {
6263
className?: string;
6364
}
6465

66+
function PrettyDateOutputRow({
67+
date,
68+
time,
69+
displayValue,
70+
}: {
71+
date: string;
72+
time: string;
73+
displayValue: string;
74+
}) {
75+
const hasPretty = Boolean(date && time);
76+
return (
77+
<div className="group relative px-1 py-1.5 h-full w-full flex flex-col border-b border-border items-center justify-center text-center text-base">
78+
{hasPretty ? (
79+
<>
80+
<span className="font-sans font-bold">{date}</span>{" "}
81+
<span className="font-sans font-medium">{time}</span>
82+
</>
83+
) : (
84+
displayValue
85+
)}
86+
<div className="font-sans absolute right-1 top-1 flex gap-0 opacity-0 transition-opacity group-hover:opacity-100">
87+
<CopyButton value={displayValue} ariaLabel={`copy pretty date`} />
88+
</div>
89+
</div>
90+
);
91+
}
92+
6593
function TimestampOutputRow({
6694
label,
6795
value,
@@ -70,11 +98,11 @@ function TimestampOutputRow({
7098
return (
7199
<div
72100
className={cn(
73-
"group relative font-mono px-1 py-1.5 text-xs h-8",
101+
"group relative font-mono px-1 py-1.5 text-xs h-8 border-b border-border",
74102
className
75103
)}
76104
>
77-
<span className="text-muted-foreground bg-accent/50 p-1">{label}:</span>{" "}
105+
<span className="text-muted-foreground bg-accent/50 p-1">{label}</span>{" "}
78106
{value}
79107
<div className="font-sans absolute right-1 top-1 flex gap-0 opacity-0 transition-opacity group-hover:opacity-100">
80108
<CopyButton value={value} ariaLabel={`copy ${label}`} />

0 commit comments

Comments
 (0)