├── public ├── apple-icon.png ├── placeholder.jpg ├── icon-dark-32x32.png ├── icon-light-32x32.png ├── placeholder-logo.png ├── placeholder-user.jpg ├── icon.svg ├── placeholder-logo.svg └── placeholder.svg ├── postcss.config.mjs ├── lib ├── utils.ts ├── supabase │ ├── client.ts │ ├── middleware.ts │ └── server.ts └── i18n │ ├── context.tsx │ └── translations.ts ├── next.config.mjs ├── scripts ├── 004_enable_realtime.sql ├── 003_create_views.sql ├── 006_update_rls_for_new_tables.sql ├── 005_add_answer_votes_and_followups.sql ├── 001_create_tables.sql └── 002_enable_rls.sql ├── components ├── theme-provider.tsx ├── language-switcher.tsx ├── ui │ ├── label.tsx │ ├── separator.tsx │ ├── textarea.tsx │ ├── input.tsx │ ├── switch.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── button.tsx │ ├── card.tsx │ └── dropdown-menu.tsx ├── qr-code.tsx ├── footer.tsx ├── ama-list.tsx ├── user-menu.tsx ├── submit-question-form.tsx ├── public-ama-list.tsx ├── ama-settings.tsx ├── create-ama-form.tsx ├── questions-list.tsx └── public-questions-list.tsx ├── middleware.ts ├── components.json ├── tsconfig.json ├── app ├── dashboard │ ├── create │ │ └── page.tsx │ ├── page.tsx │ └── ama │ │ └── [id] │ │ └── page.tsx ├── layout.tsx ├── auth │ ├── check-email │ │ └── page.tsx │ ├── login │ │ └── page.tsx │ └── sign-up │ │ └── page.tsx ├── globals.css ├── page.tsx └── ama │ └── [slug] │ └── page.tsx ├── README.md ├── package.json └── styles └── globals.css /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/apple-icon.png -------------------------------------------------------------------------------- /public/placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/placeholder.jpg -------------------------------------------------------------------------------- /public/icon-dark-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/icon-dark-32x32.png -------------------------------------------------------------------------------- /public/icon-light-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/icon-light-32x32.png -------------------------------------------------------------------------------- /public/placeholder-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/placeholder-logo.png -------------------------------------------------------------------------------- /public/placeholder-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/node/v0-askma/main/public/placeholder-user.jpg -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | '@tailwindcss/postcss': {}, 5 | }, 6 | } 7 | 8 | export default config 9 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from 'clsx' 2 | import { twMerge } from 'tailwind-merge' 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | typescript: { 4 | ignoreBuildErrors: true, 5 | }, 6 | images: { 7 | unoptimized: true, 8 | }, 9 | 10 | eslint: { 11 | ignoreDuringBuilds: true, 12 | }, 13 | } 14 | 15 | export default nextConfig -------------------------------------------------------------------------------- /scripts/004_enable_realtime.sql: -------------------------------------------------------------------------------- 1 | -- Enable realtime for all tables 2 | ALTER PUBLICATION supabase_realtime ADD TABLE public.questions; 3 | ALTER PUBLICATION supabase_realtime ADD TABLE public.answers; 4 | ALTER PUBLICATION supabase_realtime ADD TABLE public.votes; 5 | ALTER PUBLICATION supabase_realtime ADD TABLE public.amas; 6 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import { 5 | ThemeProvider as NextThemesProvider, 6 | type ThemeProviderProps, 7 | } from 'next-themes' 8 | 9 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 10 | return {children} 11 | } 12 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { updateSession } from "@/lib/supabase/middleware" 2 | import type { NextRequest } from "next/server" 3 | 4 | export async function middleware(request: NextRequest) { 5 | return await updateSession(request) 6 | } 7 | 8 | export const config = { 9 | matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"], 10 | } 11 | -------------------------------------------------------------------------------- /scripts/003_create_views.sql: -------------------------------------------------------------------------------- 1 | -- Create a view for questions with vote counts 2 | CREATE OR REPLACE VIEW public.questions_with_votes AS 3 | SELECT 4 | q.*, 5 | COUNT(v.id) as vote_count, 6 | EXISTS(SELECT 1 FROM public.answers WHERE answers.question_id = q.id) as has_answer 7 | FROM public.questions q 8 | LEFT JOIN public.votes v ON v.question_id = q.id 9 | GROUP BY q.id; 10 | 11 | -- Grant access to the view 12 | GRANT SELECT ON public.questions_with_votes TO authenticated, anon; 13 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /components/language-switcher.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Button } from "@/components/ui/button" 4 | import { useLanguage } from "@/lib/i18n/context" 5 | import { Languages } from "lucide-react" 6 | 7 | export function LanguageSwitcher() { 8 | const { language, setLanguage } = useLanguage() 9 | 10 | const toggleLanguage = () => { 11 | setLanguage(language === "en" ? "zh" : "en") 12 | } 13 | 14 | return ( 15 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "target": "ES6", 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /lib/supabase/client.ts: -------------------------------------------------------------------------------- 1 | import { createBrowserClient } from "@supabase/ssr" 2 | 3 | export function createClient() { 4 | const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL 5 | const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY 6 | 7 | if (!supabaseUrl || !supabaseAnonKey) { 8 | console.error("[v0] Missing Supabase environment variables:", { 9 | hasUrl: !!supabaseUrl, 10 | hasKey: !!supabaseAnonKey, 11 | }) 12 | throw new Error( 13 | "Missing Supabase environment variables. Please add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY to your environment variables.", 14 | ) 15 | } 16 | 17 | return createBrowserClient(supabaseUrl, supabaseAnonKey) 18 | } 19 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as LabelPrimitive from '@radix-ui/react-label' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | import * as React from 'react' 4 | import * as SeparatorPrimitive from '@radix-ui/react-separator' 5 | 6 | import { cn } from '@/lib/utils' 7 | 8 | function Separator({ 9 | className, 10 | orientation = 'horizontal', 11 | decorative = true, 12 | ...props 13 | }: React.ComponentProps) { 14 | return ( 15 | 25 | ) 26 | } 27 | 28 | export { Separator } 29 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | import { cn } from '@/lib/utils' 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) { 6 | return ( 7 |