diff options
| -rw-r--r-- | package.json | 9 | ||||
| -rw-r--r-- | postcss.config.js | 9 | ||||
| -rw-r--r-- | src/Widget.tsx | 52 | ||||
| -rw-r--r-- | src/components/app-sidebar.tsx | 82 | ||||
| -rw-r--r-- | src/components/nav-main.tsx | 94 | ||||
| -rw-r--r-- | src/components/site-header.tsx | 9 | ||||
| -rw-r--r-- | src/index.css | 19 | ||||
| -rw-r--r-- | src/main.tsx | 8 | ||||
| -rw-r--r-- | src/pages/Demographics.tsx | 2 | ||||
| -rw-r--r-- | vite.config.mts | 44 |
10 files changed, 188 insertions, 140 deletions
diff --git a/package.json b/package.json index 3acfdad..8b704ab 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "lucide-react": "^0.503.0", "motion": "^12.12.1", "next-themes": "^0.4.6", + "postcss-cli": "^11.0.1", "querystring": "^0.2.1", "react": "^18.3.1", "react-day-picker": "^8.10.1", @@ -66,22 +67,28 @@ "recharts": "^2.15.3", "sonner": "^2.0.3", "tailwind-merge": "^3.2.0", - "tailwindcss": "^4.1.4", "tw-animate-css": "^1.2.8", "vaul": "^1.1.2", + "vite-plugin-css-injected-by-js": "^3.5.2", "zod": "^3.24.3" }, "devDependencies": { "@eslint/js": "^9.17.0", + "@tailwindcss/postcss": "^4.1.10", "@types/node": "^22.14.1", "@types/react": "^18.3.20", "@types/react-dom": "^18.3.6", "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "cssnano": "^7.0.7", "eslint": "^9.29.0", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.20", "globals": "^15.15.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.10", + "terser": "^5.43.1", "typescript": "~5.6.2", "typescript-eslint": "^8.31.0", "vite": "^6.3.2", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2d47f24 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,9 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + cssnano: { + preset: ['default', {discardComments: {removeAll: true}}], + }, + }, +} diff --git a/src/Widget.tsx b/src/Widget.tsx index 192e4e0..4f4556b 100644 --- a/src/Widget.tsx +++ b/src/Widget.tsx @@ -1,5 +1,5 @@ -import {useEffect} from 'react' -import {AppSidebar} from "@/components/app-sidebar" +import { useEffect, useState, useRef } from "react" + import {SiteHeader} from "@/components/site-header" import {SidebarInset, SidebarProvider} from "@/components/ui/sidebar" import {Offerwall} from "@/pages/Offerwall.tsx" @@ -8,6 +8,7 @@ import {Demographics} from "@/pages/Demographics.tsx" import {CashoutMethodsPage} from "@/pages/CashoutMethods.tsx"; import {TransactionHistoryPage} from "@/pages/TransactionHistory.tsx" import {TaskAttemptHistoryPage} from "@/pages/TaskAttemptHistory.tsx"; +import {AppSidebar} from "@/components/app-sidebar" import {useAppDispatch, useAppSelector} from "@/hooks.ts"; import { @@ -33,11 +34,34 @@ import {setMarketplaceAnswers} from "@/models/userMarketplaceAnswerSlice.ts"; import {setUserProfile} from "@/models/userProfileSlice.ts"; import {setTaskStatuses} from "@/models/taskStatusSlice.ts" import {App} from "@/models/app.ts" +import {ScrollArea} from "@/components/ui/scroll-area" import './index.css'; +export function useParentHeight() { + const ref = useRef<HTMLDivElement>(null) + const [height, setHeight] = useState<number | null>(null) + + useEffect(() => { + if (!ref.current) return + + const observer = new ResizeObserver(() => { + if (ref.current?.parentElement) { + const h = ref.current.parentElement.getBoundingClientRect().height + setHeight(h) + } + }) + + observer.observe(ref.current.parentElement!) + + return () => observer.disconnect() + }, []) + + return { ref, height } +} const Widget = () => { + const { ref, height } = useParentHeight() const dispatch = useAppDispatch() const app: App = useAppSelector(state => state.app) @@ -103,31 +127,29 @@ const Widget = () => { }, []); // ← empty array means "run once" - return ( - <SidebarProvider> - <AppSidebar variant="floating"/> - <SidebarInset> - <SiteHeader/> + <div ref={ref} className="w-full h-full bg-white rounded-lg shadow relative"> + <SidebarProvider className="bg-blend-darken h-full flex flex-1" style={{ height }}> + <AppSidebar className="h-full" style={{ height }}/> - <div className="flex flex-1 flex-col"> - <div className="@container/main flex flex-1 flex-col gap-2"> + <SidebarInset className="h-full" style={{ height }}> + <SiteHeader/> + <ScrollArea className="overflow-auto px-4" style={{ maxHeight: height ? height - 24 : undefined }}> <div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6"> - <div className="px-4 lg:px-6"> + <div className="px-4 lg:px-6 bg-white"> {app.currentPage === 'offerwall' && <Offerwall/>} {app.currentPage === 'questions' && <QuestionsPage/>} {app.currentPage === 'cashout_methods' && <CashoutMethodsPage/>} {app.currentPage === 'task_attempts' && <TaskAttemptHistoryPage/>} - {app.currentPage === 'demographics' && <Demographics/>} {app.currentPage === 'transaction_history' && <TransactionHistoryPage/>} </div> </div> - </div> - </div> + </ScrollArea> - </SidebarInset> - </SidebarProvider> + </SidebarInset> + </SidebarProvider> + </div> ) } diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index c60bf17..b61a716 100644 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -1,9 +1,7 @@ "use client" import React from "react" -import {CircleDollarSign, SquareStack} from "lucide-react" - -import {NavMain} from "@/components/nav-main" +import {Activity, CircleDollarSign, ListIcon, NotebookText, SquareStack, User} from "lucide-react" import { Sidebar, SidebarContent, @@ -22,6 +20,8 @@ import {Badge} from "@/components/ui/badge.tsx"; import {useSelector} from "react-redux"; import {selectCashoutMethods} from "@/models/cashoutMethodSlice.ts"; import {selectTransactionHistory} from "@/models/transactionHistorySlice.ts"; +import {selectQuestions} from "../models/questionSlice"; +import {selectUserUpkAnswers} from "../models/userUpkAnswerSlice"; export function AppSidebar({...props}: React.ComponentProps<typeof Sidebar>) { const app: App = useAppSelector(state => state.app) @@ -29,8 +29,12 @@ export function AppSidebar({...props}: React.ComponentProps<typeof Sidebar>) { const cashoutMethods = useSelector(selectCashoutMethods) const transactionHistory = useSelector(selectTransactionHistory) + const questions = useSelector(selectQuestions) + const upkAnswers = useSelector(selectUserUpkAnswers) + const taskAttempts = useAppSelector(state => state.taskStatus) + return ( - <Sidebar collapsible="offcanvas" {...props}> + <Sidebar collapsible="none" {...props}> <SidebarHeader> <SidebarMenu> <SidebarMenuItem key="panel_name"> @@ -38,7 +42,6 @@ export function AppSidebar({...props}: React.ComponentProps<typeof Sidebar>) { asChild className="data-[slot=sidebar-menu-button]:!p-1.5" > - <span className="text-base font-semibold">{app.panelName}</span> </SidebarMenuButton> </SidebarMenuItem> @@ -46,10 +49,73 @@ export function AppSidebar({...props}: React.ComponentProps<typeof Sidebar>) { </SidebarHeader> <SidebarContent> - <NavMain></NavMain> - </SidebarContent> + <SidebarGroup> + <SidebarGroupContent className="flex flex-col gap-2"> + <SidebarMenu> + + <SidebarMenuItem key="surveys" + onClick={() => dispatch(setPage("offerwall"))} + > + <SidebarMenuButton tooltip="Surveys"> + <NotebookText/> + <span> + Surveys <Badge + className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + title={`${(app.availability_count ?? 0).toLocaleString()} live surveys`} + >{(app.availability_count ?? 0).toLocaleString()}</Badge> + </span> + </SidebarMenuButton> + </SidebarMenuItem> + + + <SidebarMenuItem key="questions" + onClick={() => dispatch(setPage("questions"))} + > + <SidebarMenuButton tooltip="Questions"> + <ListIcon/> + <span> + Questions <Badge + className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + title={`${questions.length.toLocaleString()} profiling question available`} + >{questions.length.toLocaleString()}</Badge> + </span> + </SidebarMenuButton> + </SidebarMenuItem> + + <SidebarMenuItem key="demographics" + onClick={() => dispatch(setPage("demographics"))} + > + <SidebarMenuButton tooltip="User Demographics"> + <User/> + <span> + Demographics <Badge + className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + >{upkAnswers.length.toLocaleString()}</Badge> + </span> + </SidebarMenuButton> + </SidebarMenuItem> + + <SidebarMenuItem key="task_attempts" + onClick={() => dispatch(setPage("task_attempts"))} + > + <SidebarMenuButton tooltip="Survey History"> + <Activity/> + <span> + Survey History <Badge + className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" + variant="outline" + >{taskAttempts.length.toLocaleString()}</Badge> + </span> + </SidebarMenuButton> + </SidebarMenuItem> + + </SidebarMenu> + </SidebarGroupContent> + </SidebarGroup> - <SidebarContent> <SidebarGroup> <SidebarGroupLabel>Redemption</SidebarGroupLabel> <SidebarGroupContent> diff --git a/src/components/nav-main.tsx b/src/components/nav-main.tsx deleted file mode 100644 index 9ed1b24..0000000 --- a/src/components/nav-main.tsx +++ /dev/null @@ -1,94 +0,0 @@ -"use client" - -import {ListIcon, NotebookText, Users, User, Activity} from "lucide-react" -import { - SidebarGroup, - SidebarGroupContent, - SidebarMenu, - SidebarMenuButton, - SidebarMenuItem, -} from "@/components/ui/sidebar" -import {setPage} from "@/models/appSlice.ts"; -import {useAppDispatch, useAppSelector} from "@/hooks.ts"; -import {useSelector} from "react-redux"; -import {selectQuestions} from "@/models/questionSlice.ts"; -import {Badge} from "@/components/ui/badge" -import {selectUserUpkAnswers} from "@/models/userUpkAnswerSlice.ts"; - -export function NavMain() { - const dispatch = useAppDispatch() - - const app = useAppSelector(state => state.app) - const questions = useSelector(selectQuestions) - const upkAnswers = useSelector(selectUserUpkAnswers) - const taskAttempts = useAppSelector(state => state.taskStatus) - - return ( - <SidebarGroup> - <SidebarGroupContent className="flex flex-col gap-2"> - <SidebarMenu> - - <SidebarMenuItem key="surveys" - onClick={() => dispatch(setPage("offerwall"))} - > - <SidebarMenuButton tooltip="Surveys"> - <NotebookText/> - <span> - Surveys <Badge - className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" - variant="outline" - title={`${(app.availability_count ?? 0).toLocaleString()} live surveys`} - >{(app.availability_count ?? 0).toLocaleString()}</Badge> - </span> - </SidebarMenuButton> - </SidebarMenuItem> - - - <SidebarMenuItem key="questions" - onClick={() => dispatch(setPage("questions"))} - > - <SidebarMenuButton tooltip="Questions"> - <ListIcon/> - <span> - Questions <Badge - className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" - variant="outline" - title={`${questions.length.toLocaleString()} profiling question available`} - >{questions.length.toLocaleString()}</Badge> - </span> - </SidebarMenuButton> - </SidebarMenuItem> - - <SidebarMenuItem key="demographics" - onClick={() => dispatch(setPage("demographics"))} - > - <SidebarMenuButton tooltip="User Demographics"> - <User/> - <span> - Demographics <Badge - className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" - variant="outline" - >{upkAnswers.length.toLocaleString()}</Badge> - </span> - </SidebarMenuButton> - </SidebarMenuItem> - - <SidebarMenuItem key="task_attempts" - onClick={() => dispatch(setPage("task_attempts"))} - > - <SidebarMenuButton tooltip="Survey History"> - <Activity/> - <span> - Survey History <Badge - className="absolute top-2 right-2 h-5 min-w-5 rounded-full px-1 font-mono tabular-nums cursor-pointer" - variant="outline" - >{taskAttempts.length.toLocaleString()}</Badge> - </span> - </SidebarMenuButton> - </SidebarMenuItem> - - </SidebarMenu> - </SidebarGroupContent> - </SidebarGroup> - ) -} diff --git a/src/components/site-header.tsx b/src/components/site-header.tsx index dca4996..70a2068 100644 --- a/src/components/site-header.tsx +++ b/src/components/site-header.tsx @@ -1,8 +1,6 @@ import {Separator} from "@/components/ui/separator" -import {SidebarTrigger} from "@/components/ui/sidebar" import {useAppSelector} from "@/hooks.ts"; import {App} from "@/models/app.ts" -import {Offerwall} from "@/pages/Offerwall.tsx"; const SiteHeader = () => { const app: App = useAppSelector(state => state.app) @@ -10,13 +8,8 @@ const SiteHeader = () => { return ( <header className="group-has-data-[collapsible=icon]/sidebar-wrapper:h-12 flex h-12 shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear"> - <div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6"> - <SidebarTrigger/> + <div className="flex w-full items-center gap-1 px-2 lg:gap-2 lg:px-4"> - <Separator - orientation="vertical" - className="mx-2 data-[orientation=vertical]:h-4" - /> <h1 className="text-base font-medium"> {app.currentPage === 'offerwall' && "Offerwall"} {app.currentPage === 'questions' && "Profiling Questions"} diff --git a/src/index.css b/src/index.css index e3beb23..39f436b 100644 --- a/src/index.css +++ b/src/index.css @@ -4,11 +4,6 @@ @tailwind components; @tailwind utilities; -#testD4rN { - max-width: 800px; - max-height: 400px !important; -} - @custom-variant dark (&:is(.dark *)); @theme inline { @@ -145,4 +140,16 @@ body { @apply bg-background text-foreground; } -}
\ No newline at end of file +} + +#testD4rN { + /*max-width: 800px;*/ + /*max-height: 400px !important;*/ + + height: 500px; + width: 800px; + border: 1px solid red; + position: absolute; + top: 80px; + left: 80px; +} diff --git a/src/main.tsx b/src/main.tsx index 3dfd07a..c944cdd 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -6,6 +6,8 @@ import {Provider} from 'react-redux' import {store} from './store' import {setApp} from "@/models/appSlice.ts"; +import "@/index.css" + (function () { // Finding the script tag on the page.. @@ -42,9 +44,9 @@ import {setApp} from "@/models/appSlice.ts"; const root = createRoot(container) root.render( // <React.StrictMode> - <Provider store={store}> - <Widget/> - </Provider> + <Provider store={store}> + <Widget/> + </Provider> // </React.StrictMode> ); diff --git a/src/pages/Demographics.tsx b/src/pages/Demographics.tsx index 2031511..f7f6efa 100644 --- a/src/pages/Demographics.tsx +++ b/src/pages/Demographics.tsx @@ -101,7 +101,7 @@ export const ContactCard = () => { <CardContent className="space-y-2 pt-2"> <div className="flex items-center space-x-2 text-sm text-muted-foreground"> <User className="w-4 h-4"/> - <span>{titleCase(gender as string) ?? " - "}</span> + <span>{titleCase(gender ?? " - " as string)}</span> </div> <div className="flex items-center space-x-2 text-sm text-muted-foreground"> <Calendar className="w-4 h-4"/> diff --git a/vite.config.mts b/vite.config.mts index cf45ffb..7752a18 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -1,12 +1,18 @@ import path from "path" -import tailwindcss from "@tailwindcss/vite" +// import tailwindcss from "@tailwindcss/vite" import react from "@vitejs/plugin-react" import {defineConfig} from "vite" -import {viteSingleFile} from "vite-plugin-singlefile" +import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' + +// ESM-compatible imports +// const tailwindcss = (await import('tailwindcss')).default +const tailwindcss = (await import('@tailwindcss/postcss')).default +const autoprefixer = (await import('autoprefixer')).default +const cssnano = (await import('cssnano')).default export default defineConfig({ - plugins: [react(), tailwindcss(), viteSingleFile()], + plugins: [cssInjectedByJsPlugin(), react()], resolve: { alias: { "@": path.resolve(__dirname, "./src"), @@ -17,16 +23,46 @@ export default defineConfig({ port: 8080, }, + css: { + postcss: { + plugins: [ + tailwindcss, + autoprefixer, + cssnano({ + preset: ['default', { + discardComments: { removeAll: true }, + }], + }), + ], + } + }, + + define: { + 'process.env.NODE_ENV': JSON.stringify('production') + }, + build: { target: "es2015", + minify: 'terser', + cssCodeSplit: false, + terserOptions: { + format: { + comments: false, + }, + compress: { + drop_console: true, + drop_debugger: true + } + }, lib: { entry: 'src/main.tsx', name: 'GRLWidget', formats: ['iife'], - fileName: 'my-widget.js' + fileName: 'my-widget' }, rollupOptions: { output: { + entryFileNames: 'grl-panel.js', globals: { react: 'React', 'react-dom': 'ReactDOM' |
