fixed bugs

This commit is contained in:
Anant-0705 2026-07-15 21:31:47 +05:30
parent a993498b5f
commit 82c313eb69
16 changed files with 2409 additions and 757 deletions

View File

@ -1,5 +1,5 @@
import { Hono, MiddlewareHandler } from 'hono';
import { AppContext, UserData } from '../utils';
import { AppContext, UserData, authMiddleware } from '../utils';
// Define types based on schema for clarity
type Post = {
@ -24,18 +24,15 @@ const EMOTION_TAGS = ['Sadness', 'Rage', 'Joy', 'Anxiety', 'Love', 'Grief', 'Exc
const publicRoutes = new Hono<AppContext>();
// Middleware to extract and validate the anonymous user ID
const anonymousUserMiddleware: MiddlewareHandler<AppContext> = async (c, next) => {
const anonymousUserId = c.req.header('X-Anonymous-User-ID');
if (!anonymousUserId) {
return c.json({ success: false, error: 'X-Anonymous-User-ID header is required.' }, 400);
}
c.set('anonymousUserId', anonymousUserId);
await next();
};
// We no longer use anonymousUserMiddleware, we use authMiddleware from utils
publicRoutes.post('/user/check-in', authMiddleware, async (c) => {
// authMiddleware ensures the user is valid and inserted into the DB
const user = c.get('user');
return c.json({ success: true, user });
});
// CRITICAL: R2 File upload route (as per general instructions)
publicRoutes.post('/upload', anonymousUserMiddleware, async (c) => {
publicRoutes.post('/upload', authMiddleware, async (c) => {
const body = await c.req.parseBody();
const file = body['file'];
@ -91,8 +88,9 @@ publicRoutes.get('/media/:key', async (c) => {
// 2. Create Post
publicRoutes.post('/posts', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.post('/posts', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email; // Map authenticated user to anonymous_user_id
const body = await c.req.json();
const { contentType, contentData, emotionTag } = body;
@ -123,7 +121,7 @@ publicRoutes.post('/posts', anonymousUserMiddleware, async (c) => {
const { success } = await c.env.DB.prepare(
`INSERT INTO posts (id, anonymous_user_id, content_type, content_data, emotion_tag, created_at, expires_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`
).bind(postId, anonymousUserId, contentType, contentData, emotionTag, createdAt, expiresAtISO).run();
).bind(postId, userId, contentType, contentData, emotionTag, createdAt, expiresAtISO).run();
if (!success) {
return c.json({ success: false, error: 'Failed to create post.' }, 500);
@ -183,15 +181,16 @@ publicRoutes.get('/posts', async (c) => {
});
// 4. Get User's Own Active Posts
publicRoutes.get('/posts/me', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.get('/posts/me', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email;
try {
const { results } = await c.env.DB.prepare(
`SELECT p.*, (SELECT COUNT(*) FROM holds h WHERE h.post_id = p.id) as holdCount
FROM posts p
WHERE p.anonymous_user_id = ? AND p.expires_at > CURRENT_TIMESTAMP
ORDER BY p.created_at DESC`
).bind(anonymousUserId).all<Post & { holdCount: number }>();
).bind(userId).all<Post & { holdCount: number }>();
return c.json({ posts: results || [] });
@ -202,8 +201,9 @@ publicRoutes.get('/posts/me', anonymousUserMiddleware, async (c) => {
});
// 5. Hold a Post
publicRoutes.post('/posts/:postId/hold', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.post('/posts/:postId/hold', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email;
const { postId } = c.req.param();
try {
@ -215,7 +215,7 @@ publicRoutes.post('/posts/:postId/hold', anonymousUserMiddleware, async (c) => {
const { success } = await c.env.DB.prepare(
'INSERT INTO holds (post_id, anonymous_user_id) VALUES (?, ?)'
).bind(postId, anonymousUserId).run();
).bind(postId, userId).run();
if (!success) {
// This likely means a UNIQUE constraint violation
@ -236,8 +236,9 @@ publicRoutes.post('/posts/:postId/hold', anonymousUserMiddleware, async (c) => {
// 6. Report a Post
publicRoutes.post('/posts/:postId/report', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.post('/posts/:postId/report', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email;
const { postId } = c.req.param();
const body = await c.req.json();
const reason = body.reason || null;
@ -249,7 +250,7 @@ publicRoutes.post('/posts/:postId/report', anonymousUserMiddleware, async (c) =>
}
const existingReport = await c.env.DB.prepare("SELECT id FROM reports WHERE post_id = ? AND reporter_anonymous_user_id = ?")
.bind(postId, anonymousUserId)
.bind(postId, userId)
.first();
if (existingReport) {
@ -258,7 +259,7 @@ publicRoutes.post('/posts/:postId/report', anonymousUserMiddleware, async (c) =>
await c.env.DB.prepare(
'INSERT INTO reports (post_id, reporter_anonymous_user_id, reason) VALUES (?, ?, ?)'
).bind(postId, anonymousUserId, reason).run();
).bind(postId, userId, reason).run();
return c.body(null, 202);
} catch (e: any) {
@ -268,15 +269,16 @@ publicRoutes.post('/posts/:postId/report', anonymousUserMiddleware, async (c) =>
});
// 7. Get Post Summaries
publicRoutes.get('/posts/summaries', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.get('/posts/summaries', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email;
try {
const { results } = await c.env.DB.prepare(
`SELECT id, emotion_tag, hold_count, dissolved_at
FROM post_summaries
WHERE anonymous_user_id = ?
ORDER BY dissolved_at DESC`
).bind(anonymousUserId).all<PostSummary>();
).bind(userId).all<PostSummary>();
return c.json({ summaries: results || [] });
} catch (e: any) {
@ -286,8 +288,9 @@ publicRoutes.get('/posts/summaries', anonymousUserMiddleware, async (c) => {
});
// 8. Clear Post Summaries
publicRoutes.post('/posts/summaries/clear', anonymousUserMiddleware, async (c) => {
const anonymousUserId = c.get('anonymousUserId');
publicRoutes.post('/posts/summaries/clear', authMiddleware, async (c) => {
const user = c.get('user');
const userId = user.email;
const body = await c.req.json();
const summaryIds = body.summaryIds;
@ -297,7 +300,7 @@ publicRoutes.post('/posts/summaries/clear', anonymousUserMiddleware, async (c) =
try {
const query = `DELETE FROM post_summaries WHERE anonymous_user_id = ? AND id IN (${summaryIds.map(() => '?').join(',')})`;
await c.env.DB.prepare(query).bind(anonymousUserId, ...summaryIds).run();
await c.env.DB.prepare(query).bind(userId, ...summaryIds).run();
return c.body(null, 204);
} catch (e: any) {

View File

@ -6,7 +6,7 @@
{
"binding": "DB",
"database_name": "db-xd1uom",
"database_id": "23b00c58-c2c5-4f98-abba-ce49c8cfaa0c"
"database_id": "c998402c-77a7-4d05-b963-11fe654161b9"
}
],
"r2_buckets": [

File diff suppressed because it is too large Load Diff

View File

@ -2,11 +2,10 @@ import { Routes, Route } from 'react-router-dom';
import { Toaster } from 'sonner';
import { Layout } from '@/components/Layout';
import { StreamPage, CreatePostPage, MyDissolvesPage, NotFoundPage } from '@/pages';
import { AnonymousIdProvider } from '@/context/AnonymousIdContext';
function App() {
return (
<AnonymousIdProvider>
<>
<Layout>
<Routes>
<Route path="/" element={<StreamPage />} />
@ -16,7 +15,7 @@ function App() {
</Routes>
</Layout>
<Toaster position="bottom-center" toastOptions={{ className: 'font-sans' }} />
</AnonymousIdProvider>
</>
);
}

View File

@ -1,9 +1,9 @@
import axios from 'axios';
export const API_URL = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')
? "http://127.0.0.1:8787"
: "https://app-xd1uom.laflabs-tech.workers.dev";
export const API_URL = (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' || window.location.hostname.startsWith('192.168.'))
? `http://${window.location.hostname}:8787`
: "https://app-xd1uom.mworld-projects.workers.dev";
export const api = axios.create({
baseURL: API_URL

View File

@ -12,6 +12,7 @@ export function AudioPlayer({ src }: AudioPlayerProps) {
const [duration, setDuration] = useState(0);
const [currentTime, setCurrentTime] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [hasError, setHasError] = useState(false);
useEffect(() => {
const audio = audioRef.current;
@ -24,17 +25,36 @@ export function AudioPlayer({ src }: AudioPlayerProps) {
const handleTimeUpdate = () => setCurrentTime(audio.currentTime);
const handleEnded = () => setIsPlaying(false);
const handleCanPlay = () => setIsLoading(false);
const handleError = () => {
console.error("Error loading audio file:", audio.error);
setIsLoading(false);
setHasError(true);
};
audio.addEventListener('loadedmetadata', handleLoadedMetadata);
audio.addEventListener('timeupdate', handleTimeUpdate);
audio.addEventListener('ended', handleEnded);
audio.addEventListener('canplay', handleCanPlay);
audio.addEventListener('error', handleError);
// Check if audio has already loaded enough data before listeners were attached
if (audio.readyState >= 1) {
handleLoadedMetadata();
}
if (audio.readyState >= 3) {
handleCanPlay();
}
if (audio.error) {
handleError();
}
return () => {
audio.removeEventListener('loadedmetadata', handleLoadedMetadata);
audio.removeEventListener('timeupdate', handleTimeUpdate);
audio.removeEventListener('timeupdate', handleTimeUpdate);
audio.removeEventListener('ended', handleEnded);
audio.removeEventListener('canplay', handleCanPlay);
audio.removeEventListener('error', handleError);
};
}, []);
@ -54,11 +74,13 @@ export function AudioPlayer({ src }: AudioPlayerProps) {
<audio ref={audioRef} src={src} preload="metadata"></audio>
<button
onClick={togglePlayPause}
disabled={isLoading}
disabled={isLoading || hasError}
className="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-full bg-primary text-on-primary disabled:bg-primary-disabled"
>
{isLoading ? (
<Loader2 className="h-5 w-5 animate-spin" />
) : hasError ? (
<span className="text-xs">!</span>
) : isPlaying ? (
<Pause className="h-5 w-5" />
) : (

View File

@ -1,7 +1,8 @@
import React from 'react';
import { NavLink, useLocation, Link } from 'react-router-dom';
import { Plus } from 'lucide-react';
import { Plus, LogIn, LogOut, User } from 'lucide-react';
import { cn } from '@/lib/utils';
import { useAuth } from '@/context/AuthContext';
interface LayoutProps {
children: React.ReactNode;
@ -9,11 +10,12 @@ interface LayoutProps {
export function Layout({ children }: LayoutProps) {
const location = useLocation();
const { isAuthenticated, login, logout, user } = useAuth();
const navItems = [
{ path: '/', label: 'The Stream' },
{ path: '/my-dissolves', label: 'My Dissolves' },
];
{ path: '/', label: 'The Stream', public: true },
{ path: '/my-dissolves', label: 'My Dissolves', public: false },
].filter(item => item.public || isAuthenticated);
return (
<div className="min-h-screen bg-canvas font-sans">
@ -42,9 +44,28 @@ export function Layout({ children }: LayoutProps) {
})}
</nav>
</div>
<Link to="/create" className="inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium h-10 px-6 bg-primary text-on-primary hover:bg-primary-active transition-colors">
<Plus className="mr-2 h-4 w-4" /> Share a Feeling
</Link>
<div className="flex items-center gap-4">
{isAuthenticated ? (
<>
<Link to="/create" className="hidden md:inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium h-10 px-6 bg-primary text-on-primary hover:bg-primary-active transition-colors">
<Plus className="mr-2 h-4 w-4" /> Share a Feeling
</Link>
<div className="flex items-center gap-3 border-l border-hairline pl-4 ml-2">
<div className="flex items-center gap-2 text-sm text-ink font-medium">
<User className="h-4 w-4 text-muted" />
<span className="hidden sm:inline">{user?.fullName || 'User'}</span>
</div>
<button onClick={logout} className="p-2 text-muted hover:text-ink transition-colors" aria-label="Log out">
<LogOut className="h-5 w-5" />
</button>
</div>
</>
) : (
<button onClick={login} className="inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium h-10 px-6 border border-hairline text-ink hover:bg-surface-soft transition-colors">
<LogIn className="mr-2 h-4 w-4" /> Log In
</button>
)}
</div>
</div>
</header>
<main className="container-full py-8 md:py-12">

View File

@ -6,6 +6,7 @@ import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import { useApi } from '@/hooks/useApi';
import { useAuth } from '@/context/AuthContext';
import type { Post } from '@/types';
import { AudioPlayer } from './AudioPlayer';
import { ReportModal } from './ReportModal';
@ -21,8 +22,13 @@ export function PostCard({ post }: PostCardProps) {
const isHeld = useIsPostHeld(post.id) ?? false;
const [isReportModalOpen, setIsReportModalOpen] = useState(false);
const { isAuthenticated, login } = useAuth();
const handleHold = () => {
if (!isAuthenticated) {
login();
return;
}
if (isHeld) return;
holdPost(post.id, {
onSuccess: () => {
@ -81,7 +87,13 @@ export function PostCard({ post }: PostCardProps) {
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setIsReportModalOpen(true)}
onClick={() => {
if (!isAuthenticated) {
login();
} else {
setIsReportModalOpen(true);
}
}}
className="flex h-8 w-8 items-center justify-center rounded-full text-muted transition-colors hover:bg-hairline hover:text-ink"
aria-label="Report post"
>

View File

@ -1,40 +0,0 @@
import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
interface AnonymousIdContextType {
anonymousId: string | null;
isLoading: boolean;
}
const AnonymousIdContext = createContext<AnonymousIdContextType | undefined>(undefined);
export const AnonymousIdProvider = ({ children }: { children: ReactNode }) => {
const [anonymousId, setAnonymousId] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
let storedId = localStorage.getItem('anonymous_user_id');
if (!storedId) {
storedId = uuidv4();
localStorage.setItem('anonymous_user_id', storedId);
}
setAnonymousId(storedId);
setIsLoading(false);
}, []);
const value = { anonymousId, isLoading };
return (
<AnonymousIdContext.Provider value={value}>
{children}
</AnonymousIdContext.Provider>
);
};
export const useAnonymousId = () => {
const context = useContext(AnonymousIdContext);
if (context === undefined) {
throw new Error('useAnonymousId must be used within an AnonymousIdProvider');
}
return context;
};

View File

@ -1,129 +1,123 @@
import { useMutation, useQuery, useInfiniteQuery, QueryClient } from '@tanstack/react-query';
import { api, API_URL } from '../api';
import { useAnonymousId } from '@/context/AnonymousIdContext';
import { useAuth } from '@/context/AuthContext';
import type { EmotionTag, Post, PostSummary, PostContentType } from '@/types';
// A wrapper to inject the anonymous ID header
const callApi = <T,>(method: 'get' | 'post' | 'delete', url: string, anonymousId: string, data?: any): Promise<{ data: T }> => {
const headers = { 'X-Anonymous-User-ID': anonymousId };
const callApi = <T,>(method: 'get' | 'post' | 'delete', url: string, data?: any): Promise<{ data: T }> => {
if (method === 'post' || method === 'delete') {
return api[method]<T>(url, data, { headers });
return api[method]<T>(url, data);
}
return api.get<T>(url, { headers });
return api.get<T>(url);
}
export const useApi = () => {
const { anonymousId } = useAnonymousId();
const { isAuthenticated } = useAuth();
// Stream
const useGetPosts = (emotion?: EmotionTag) => {
return useInfiniteQuery(
['posts', emotion],
async ({ pageParam: cursor }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return useInfiniteQuery({
queryKey: ['posts', emotion],
queryFn: async ({ pageParam: cursor }) => {
let url = '/api/posts?';
if (emotion) url += `emotion=${emotion}&`;
if (cursor) url += `cursor=${cursor}`;
const res = await callApi<{posts: Post[], nextCursor: string | null}>('get', url, anonymousId);
const res = await callApi<{posts: Post[], nextCursor: string | null}>('get', url);
return res.data;
},
{
getNextPageParam: (lastPage) => lastPage.nextCursor,
enabled: !!anonymousId,
}
);
getNextPageParam: (lastPage) => lastPage.nextCursor,
enabled: true, // Stream is public, no authentication required to view
});
};
// Create
const useCreatePost = (queryClient: QueryClient) => {
return useMutation(
({ contentType, contentData, emotionTag }: { contentType: PostContentType, contentData: string, emotionTag: EmotionTag }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', '/api/posts', anonymousId, { contentType, contentData, emotionTag });
return useMutation({
mutationFn: ({ contentType, contentData, emotionTag }: { contentType: PostContentType, contentData: string, emotionTag: EmotionTag }) => {
if (!isAuthenticated) throw new Error('Must be logged in to post');
return callApi('post', '/api/posts', { contentType, contentData, emotionTag });
},
{
onSuccess: () => {
queryClient.invalidateQueries(['my-posts']);
queryClient.invalidateQueries(['posts']);
},
}
);
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['my-posts'] });
queryClient.invalidateQueries({ queryKey: ['posts'] });
},
});
};
const useUploadFile = () => {
return useMutation(
(file: File) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return useMutation({
mutationFn: (file: File) => {
if (!isAuthenticated) throw new Error('Must be logged in to upload');
const formData = new FormData();
formData.append('file', file);
return callApi<{url: string}>('post', '/api/upload', anonymousId, formData);
return callApi<{url: string}>('post', '/api/upload', formData);
},
);
});
}
// My Dissolves
const useGetMyPosts = () => {
return useQuery(
['my-posts'],
() => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi<{posts: Post[]}>('get', '/api/posts/me', anonymousId).then(res => res.data);
return useQuery({
queryKey: ['my-posts'],
queryFn: () => {
if (!isAuthenticated) throw new Error('Must be logged in');
return callApi<{posts: Post[]}>('get', '/api/posts/me').then(res => res.data);
},
{ enabled: !!anonymousId }
);
enabled: isAuthenticated
});
};
const useGetSummaries = () => {
return useQuery(
['summaries'],
() => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi<{summaries: PostSummary[]}>('get', '/api/posts/summaries', anonymousId).then(res => res.data);
return useQuery({
queryKey: ['summaries'],
queryFn: () => {
if (!isAuthenticated) throw new Error('Must be logged in');
return callApi<{summaries: PostSummary[]}>('get', '/api/posts/summaries').then(res => res.data);
},
{ enabled: !!anonymousId }
);
enabled: isAuthenticated
});
};
const useClearSummaries = (queryClient: QueryClient) => {
return useMutation(
(summaryIds: string[]) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', '/api/posts/summaries/clear', anonymousId, { summaryIds });
return useMutation({
mutationFn: (summaryIds: string[]) => {
if (!isAuthenticated) throw new Error('Must be logged in');
return callApi('post', '/api/posts/summaries/clear', { summaryIds });
},
{
onSuccess: () => {
queryClient.invalidateQueries(['summaries']);
},
}
);
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['summaries'] });
},
});
};
// Interactions
const useHoldPost = (queryClient: QueryClient) => {
return useMutation(
(postId: string) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', `/api/posts/${postId}/hold`, anonymousId);
return useMutation({
mutationFn: (postId: string) => {
if (!isAuthenticated) throw new Error('Must be logged in');
return callApi('post', `/api/posts/${postId}/hold`);
},
{
onSuccess: (_data, postId) => {
queryClient.setQueryData(['held-posts', postId], true);
}
onSuccess: (_data, postId) => {
queryClient.setQueryData(['held-posts', postId], true);
}
);
});
};
const useReportPost = () => {
return useMutation(
({ postId, reason }: { postId: string, reason: string }) => {
if (!anonymousId) throw new Error('Anonymous ID not available');
return callApi('post', `/api/posts/${postId}/report`, anonymousId, { reason });
return useMutation({
mutationFn: ({ postId, reason }: { postId: string, reason: string }) => {
if (!isAuthenticated) throw new Error('Must be logged in');
return callApi('post', `/api/posts/${postId}/report`, { reason });
},
);
});
};
const useIsPostHeld = (postId: string) => {
const {data} = useQuery(['held-posts', postId], () => false, { staleTime: Infinity, cacheTime: Infinity });
const {data} = useQuery({
queryKey: ['held-posts', postId],
queryFn: () => false,
staleTime: Infinity,
cacheTime: Infinity
});
return data;
}
@ -136,3 +130,4 @@ export const useApi = () => {
return { useGetPosts, useCreatePost, useUploadFile, useGetMyPosts, useGetSummaries, useClearSummaries, useHoldPost, useReportPost, useIsPostHeld, getMediaUrl };
};

View File

@ -22,7 +22,6 @@ export const useCountdown = (targetDate: string | Date) => {
format: ['hours', 'minutes', 'seconds'],
zero: true,
delimiter: ':',
pad: { hours: 2, minutes: 2, seconds: 2 },
});
// Special handling for formatDuration output
const parts = formatted.split(':').map(p => p.padStart(2, '0'));

View File

@ -1,14 +1,9 @@
@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'Airbnb Cereal VF';
src: url('https://d21p2nvrwwh70b.cloudfront.net/fonts/Airbnb_Cereal_W_VF.woff2') format('woff2-variations');
font-weight: 100 900;
font-style: normal;
}
@layer base {
:root {
--canvas: 0 0% 100%; /* #ffffff */

View File

@ -1,5 +1,6 @@
import { useState, useRef } from 'react';
import { useState, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/context/AuthContext';
import { useQueryClient } from '@tanstack/react-query';
import { motion } from 'framer-motion';
import { toast } from 'sonner';
@ -18,6 +19,15 @@ const contentTypes: { type: PostContentType, label: string, icon: React.ElementT
export default function CreatePostPage() {
const navigate = useNavigate();
const { isAuthenticated } = useAuth();
useEffect(() => {
if (!isAuthenticated) {
navigate('/');
toast.error('You must be logged in to share a feeling.');
}
}, [isAuthenticated, navigate]);
const queryClient = useQueryClient();
const { useCreatePost, useUploadFile } = useApi();
const createPostMutation = useCreatePost(queryClient);
@ -53,8 +63,10 @@ export default function CreatePostPage() {
audioChunksRef.current = [];
recorder.ondataavailable = (event) => audioChunksRef.current.push(event.data);
recorder.onstop = () => {
const audioBlob = new Blob(audioChunksRef.current, { type: 'audio/webm' });
const audioFile = new File([audioBlob], 'recording.webm', { type: 'audio/webm' });
const mimeType = recorder.mimeType || 'audio/webm';
const ext = mimeType.includes('mp4') ? 'mp4' : 'webm';
const audioBlob = new Blob(audioChunksRef.current, { type: mimeType });
const audioFile = new File([audioBlob], `recording.${ext}`, { type: mimeType });
setFileData(audioFile);
stream.getTracks().forEach(track => track.stop()); // Stop microphone access
};

View File

@ -1,4 +1,6 @@
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useAuth } from '@/context/AuthContext';
import { useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import { AnimatePresence, motion } from 'framer-motion';
@ -10,6 +12,16 @@ import { MyPostCard } from '@/components/MyPostCard';
import type { PostSummary } from '@/types';
export default function MyDissolvesPage() {
const navigate = useNavigate();
const { isAuthenticated } = useAuth();
useEffect(() => {
if (!isAuthenticated) {
navigate('/');
toast.error('You must be logged in to view your dissolves.');
}
}, [isAuthenticated, navigate]);
const queryClient = useQueryClient();
const { useGetMyPosts, useGetSummaries, useClearSummaries } = useApi();
const { data: myPostsData, isLoading: isLoadingPosts } = useGetMyPosts();

View File

@ -32,7 +32,7 @@ module.exports = {
full: '9999px',
},
fontFamily: {
sans: ['Airbnb Cereal VF', 'Circular', 'sans-serif'],
sans: ['Inter', 'sans-serif'],
},
boxShadow: {
card: '0 0 0 1px rgba(0,0,0,0.02), 0 2px 6px rgba(0,0,0,0.04), 0 4px 8px rgba(0,0,0,0.02)',

View File

@ -17,7 +17,11 @@
"moduleResolution": "Node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx"
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"src"