Skip to content

Commit b8a0d94

Browse files
authored
Merge pull request #4 from Karrot-Tech/dev
Release v1.2.1: Rebrand (Saileela Rahasya), Inquiry Persistence & A11y Improvements
2 parents 703c100 + 9806ab0 commit b8a0d94

11 files changed

Lines changed: 188 additions & 29 deletions

File tree

web/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "web",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",

web/prisma/schema.prisma

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@ model Ticket {
2323
userId String
2424
createdAt DateTime @default(now())
2525
updatedAt DateTime @updatedAt
26-
messages Message[]
27-
user User @relation(fields: [userId], references: [id])
26+
messages Message[]
27+
user User @relation(fields: [userId], references: [id])
28+
lastReadMessageId String?
29+
isArchived Boolean @default(false)
2830
2931
@@index([userId])
3032
}

web/public/offline.html

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8">
56
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -10,6 +11,7 @@
1011
--background: #ffffff;
1112
--text: #1f2937;
1213
}
14+
1315
body {
1416
margin: 0;
1517
padding: 0;
@@ -24,6 +26,7 @@
2426
text-align: center;
2527
padding: 20px;
2628
}
29+
2730
.icon-container {
2831
width: 80px;
2932
height: 80px;
@@ -35,24 +38,28 @@
3538
margin-bottom: 24px;
3639
box-shadow: 0 10px 25px -5px rgba(204, 119, 34, 0.4);
3740
}
41+
3842
svg {
3943
width: 40px;
4044
height: 40px;
4145
color: white;
4246
}
47+
4348
h1 {
4449
font-size: 24px;
4550
font-weight: 800;
4651
margin-bottom: 8px;
4752
color: #111827;
4853
}
54+
4955
p {
5056
font-size: 16px;
5157
color: #6b7280;
5258
max-width: 280px;
5359
line-height: 1.5;
5460
margin-bottom: 32px;
5561
}
62+
5663
.retry-button {
5764
background-color: var(--ochre);
5865
color: white;
@@ -66,19 +73,26 @@
6673
letter-spacing: 0.05em;
6774
font-size: 14px;
6875
}
76+
6977
.retry-button:active {
7078
transform: scale(0.95);
7179
}
7280
</style>
7381
</head>
82+
7483
<body>
75-
<div class="icon-container">
76-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
77-
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
78-
</svg>
79-
</div>
80-
<h1>Seek Grace in Silence</h1>
81-
<p>It seems your connection is resting. Please check your network to continue your spiritual journey.</p>
82-
<button class="retry-button" onclick="window.location.reload()">Retry Connection</button>
84+
<main>
85+
<div class="icon-container">
86+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2"
87+
stroke="currentColor">
88+
<path stroke-linecap="round" stroke-linejoin="round"
89+
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
90+
</svg>
91+
</div>
92+
<h1>Seek Grace in Silence</h1>
93+
<p>It seems your connection is resting. Please check your network to continue your spiritual journey.</p>
94+
<button class="retry-button" onclick="window.location.reload()">Retry Connection</button>
95+
</main>
8396
</body>
84-
</html>
97+
98+
</html>

web/public/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"version": "1.2.0",
2+
"version": "1.2.1",
33
"buildTime": "2025-12-19T12:15:00.000Z"
44
}

web/src/actions/tickets.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,66 @@ export async function userReplyToTicket(ticketId: string, text: string) {
271271
return { success: false, error: 'Failed to send follow-up' };
272272
}
273273
}
274+
275+
export async function updateTicketReadStatus(ticketId: string, messageId: string) {
276+
try {
277+
const clerkUser = await currentUser();
278+
const userEmail = clerkUser?.emailAddresses[0]?.emailAddress;
279+
280+
if (!userEmail) {
281+
return { success: false, error: 'Unauthorized' };
282+
}
283+
284+
// Verify ownership
285+
const ticket = await prisma.ticket.findUnique({
286+
where: { id: ticketId },
287+
include: { user: true }
288+
});
289+
290+
if (!ticket || ticket.user.email !== userEmail) {
291+
return { success: false, error: 'Unauthorized' };
292+
}
293+
294+
await prisma.ticket.update({
295+
where: { id: ticketId },
296+
data: { lastReadMessageId: messageId }
297+
});
298+
299+
return { success: true };
300+
} catch (error) {
301+
console.error('Error updating read status:', error);
302+
return { success: false, error: 'Failed to update' };
303+
}
304+
}
305+
306+
export async function archiveTicket(ticketId: string) {
307+
try {
308+
const clerkUser = await currentUser();
309+
const userEmail = clerkUser?.emailAddresses[0]?.emailAddress;
310+
311+
if (!userEmail) {
312+
return { success: false, error: 'Unauthorized' };
313+
}
314+
315+
// Verify ownership
316+
const ticket = await prisma.ticket.findUnique({
317+
where: { id: ticketId },
318+
include: { user: true }
319+
});
320+
321+
if (!ticket || ticket.user.email !== userEmail) {
322+
return { success: false, error: 'Unauthorized' };
323+
}
324+
325+
await prisma.ticket.update({
326+
where: { id: ticketId },
327+
data: { isArchived: true }
328+
});
329+
330+
revalidatePath('/ask');
331+
return { success: true };
332+
} catch (error) {
333+
console.error('Error archiving ticket:', error);
334+
return { success: false, error: 'Failed to archive' };
335+
}
336+
}

web/src/app/layout.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,6 @@ export const viewport = {
7272
themeColor: "#cc7722",
7373
width: "device-width",
7474
initialScale: 1,
75-
maximumScale: 1,
76-
userScalable: false,
7775
};
7876

7977
import { ClerkProvider } from '@clerk/nextjs'

web/src/app/not-found.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use client';
2+
3+
import Layout from '@/components/layout/Layout';
4+
import Link from 'next/link';
5+
6+
export default function NotFound() {
7+
return (
8+
<Layout>
9+
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
10+
<h2 className="text-4xl font-black text-ochre mb-4">404</h2>
11+
<h3 className="text-xl font-bold text-gray-900 mb-2">Page Not Found</h3>
12+
<p className="text-gray-600 mb-8 max-w-md">
13+
The spiritual path you are looking for does not exist or has been moved.
14+
</p>
15+
<Link
16+
href="/"
17+
className="px-6 py-3 bg-ochre text-white rounded-xl font-bold uppercase tracking-wider hover:bg-orange-700 transition-colors"
18+
>
19+
Return Home
20+
</Link>
21+
</div>
22+
</Layout>
23+
);
24+
}

web/src/components/common/Modal.tsx

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
'use client';
22

33
import { X } from 'lucide-react';
4-
import { ReactNode } from 'react';
4+
import { ReactNode, useEffect, useState } from 'react';
5+
import { createPortal } from 'react-dom';
56

67
interface ModalProps {
78
isOpen: boolean;
@@ -13,19 +14,45 @@ interface ModalProps {
1314
}
1415

1516
export function Modal({ isOpen, onClose, title, children, actions, flush = false }: ModalProps) {
16-
if (!isOpen) return null;
17+
const [mounted, setMounted] = useState(false);
1718

18-
return (
19+
useEffect(() => {
20+
setMounted(true);
21+
if (isOpen) {
22+
document.body.style.overflow = 'hidden';
23+
}
24+
return () => {
25+
document.body.style.overflow = 'unset';
26+
// Clean up potentially left-over strict overflow if unmounted while open
27+
if (isOpen) {
28+
document.body.style.overflow = 'unset';
29+
}
30+
};
31+
}, [isOpen]);
32+
33+
if (!mounted || !isOpen) return null;
34+
35+
return createPortal(
1936
<div className="fixed inset-0 z-[110] flex items-center justify-center p-4">
2037
<div
2138
className="absolute inset-0 bg-black/60 backdrop-blur-[2px] transition-opacity"
2239
onClick={onClose}
40+
aria-hidden="true"
2341
/>
24-
<div className={`bg-white w-full max-w-md rounded-2xl shadow-2xl relative z-10 overflow-hidden transform transition-all animate-in zoom-in duration-300 ${flush ? 'mb-0' : 'mb-0'}`}>
42+
<div
43+
className={`bg-white w-full max-w-md rounded-2xl shadow-2xl relative z-10 overflow-hidden transform transition-all animate-in zoom-in duration-300 ${flush ? 'mb-0' : 'mb-0'}`}
44+
role="dialog"
45+
aria-modal="true"
46+
aria-labelledby="modal-title"
47+
>
2548
<div className={flush ? '' : 'p-6'}>
2649
<div className={`flex justify-between items-center ${flush ? 'p-6 pb-2' : 'mb-4'}`}>
27-
<h3 className="text-xl font-black text-gray-900 tracking-tight">{title}</h3>
28-
<button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-full transition-colors">
50+
<h3 id="modal-title" className="text-xl font-black text-gray-900 tracking-tight">{title}</h3>
51+
<button
52+
onClick={onClose}
53+
className="p-2 hover:bg-gray-100 rounded-full transition-colors"
54+
aria-label="Close modal"
55+
>
2956
<X className="w-5 h-5 text-gray-500" />
3057
</button>
3158
</div>
@@ -39,6 +66,7 @@ export function Modal({ isOpen, onClose, title, children, actions, flush = false
3966
)}
4067
</div>
4168
</div>
42-
</div>
69+
</div>,
70+
document.body
4371
);
4472
}

web/src/components/layout/Sidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export default function Sidebar() {
7373
</div>
7474

7575
<div className="mt-auto pt-6 border-t border-gray-100 flex flex-col">
76-
<p className="px-4 text-[10px] font-black text-gray-400 uppercase tracking-[0.2em] mb-3">Resources</p>
76+
<p className="px-4 text-[10px] font-black text-gray-500 uppercase tracking-[0.2em] mb-3">Resources</p>
7777
<Link
7878
href="/glossary"
7979
className={`flex items-center gap-3 px-3 py-3 rounded-2xl transition-all group mx-1 ${pathname === '/glossary'
@@ -97,8 +97,8 @@ export default function Sidebar() {
9797
</nav>
9898

9999
<div className="p-5 text-center border-t border-gray-50 bg-gray-50/30">
100-
<p className="text-[9px] font-black text-gray-300 uppercase tracking-[0.2em] mb-1">Saileela Rahasya v1.2.0</p>
101-
<p className="text-[10px] text-gray-400 font-medium font-serif italic">&copy; {new Date().getFullYear()} Saileela Rahasya</p>
100+
<p className="text-[9px] font-black text-gray-500 uppercase tracking-[0.2em] mb-1">Saileela Rahasya v1.2.1</p>
101+
<p className="text-[10px] text-gray-600 font-medium font-serif italic">&copy; {new Date().getFullYear()} Saileela Rahasya</p>
102102
</div>
103103
</aside>
104104
);

web/src/components/layout/UtilityMenu.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export default function UtilityMenu() {
242242
</SignedOut>
243243

244244
<div className="text-center">
245-
<p className="text-[9px] font-black text-gray-300 uppercase tracking-[0.3em]">Saileela Rahasya v1.2.0</p>
245+
<p className="text-[9px] font-black text-gray-500 uppercase tracking-[0.3em]">Saileela Rahasya v1.2.1</p>
246246
</div>
247247
</div>
248248
</div>

0 commit comments

Comments
 (0)