@promakeai/cli 0.2.10 → 0.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1 -1
- package/dist/registry/about-page.json +2 -2
- package/dist/registry/about-section.json +2 -2
- package/dist/registry/announcement-bar.json +2 -2
- package/dist/registry/blog-list-page.json +1 -1
- package/dist/registry/blog-section.json +2 -2
- package/dist/registry/coming-soon-page-minimal.json +1 -1
- package/dist/registry/coming-soon-page.json +2 -2
- package/dist/registry/contact-page.json +2 -2
- package/dist/registry/cta-section.json +2 -2
- package/dist/registry/faq-categorized.json +2 -2
- package/dist/registry/faq-simple.json +2 -2
- package/dist/registry/feature-section.json +2 -2
- package/dist/registry/hero-cta.json +2 -2
- package/dist/registry/hero-gradient.json +2 -2
- package/dist/registry/hero.json +2 -2
- package/dist/registry/landing-page-app.json +2 -2
- package/dist/registry/landing-page-saas.json +2 -2
- package/dist/registry/portfolio-page.json +2 -2
- package/dist/registry/pricing-section.json +2 -2
- package/dist/registry/products-page.json +1 -1
- package/dist/registry/team-page.json +1 -1
- package/dist/registry/testimonials-grid.json +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -492,7 +492,7 @@ constants.json (full)`)),console.log(C.dim(JSON.stringify(U.constantsConfig,null
|
|
|
492
492
|
`)),e8("promake create <name>","Create new project"),e8("promake add <modules...>","Add modules"),e8("promake add <page> --module-name <name>","Add page with custom name"),e8("promake remove <modules...>","Remove modules"),e8("promake sync","Sync from promake.json"),e8("promake theme --list","List theme options"),e8("promake theme --preset <name>","Apply color preset"),e8("promake list","List available modules"),e8("promake doctor","Check project health"),e8("promake doctor --runtime","Include runtime checks"),e8("promake discover --json","Project info for AI"),e8("promake seo","Sync SEO meta tags"),e8("promake usage","Show this guide"),e8("promake <command> --help","Command-specific help"),console.log()}var Rr=new $5("info").description("Show detailed information about a module").argument("<module>","Module name").option("--json","Output as JSON").action(async(D,X)=>{let Z=E2(`Fetching ${D}...`).start();try{let J=await R4(D);if(Z.stop(),!J)console.error(C.red(`Module not found: ${D}`)),process.exit(1);if(X.json){console.log(JSON.stringify(J,null,2));return}if(console.log(""),console.log(C.bold.cyan(J.name)),console.log(C.dim("─".repeat(J.name.length+4))),J.title)console.log(`${C.gray("Title:")} ${J.title}`);if(console.log(`${C.gray("Type:")} ${J.type.replace("registry:","")}`),J.description)console.log(`${C.gray("Description:")} ${J.description}`);if(J.registryDependencies?.length)console.log(""),console.log(C.gray("Dependencies:")),J.registryDependencies.forEach((Y)=>{let $=!Y.includes("-")||["button","card","input","badge","dialog","sheet","dropdown-menu","accordion","tabs","select","checkbox","slider","separator","avatar","tooltip","popover","navigation-menu","scroll-area"].includes(Y)?C.dim("(shadcn)"):"";console.log(` ${C.yellow("•")} ${Y} ${$}`)});if(J.dependencies?.length)console.log(""),console.log(C.gray("NPM Packages:")),J.dependencies.forEach((Y)=>{console.log(` ${C.blue("•")} ${Y}`)});if(J.files?.length)console.log(""),console.log(C.gray("Files:")),J.files.forEach((Y)=>{console.log(` ${C.dim("•")} ${Y.path}`)});if(J.exports){if(console.log(""),console.log(C.gray("Exports:")),J.exports.types?.length)console.log(` ${C.magenta("Types:")} ${J.exports.types.join(", ")}`);if(J.exports.variables?.length)console.log(` ${C.green("Components:")} ${J.exports.variables.join(", ")}`)}if(J.route)console.log(""),console.log(C.gray("Route:")),console.log(` ${C.cyan("Path:")} ${J.route.path}`),console.log(` ${C.cyan("Component:")} ${J.route.componentName}`);if(J.usage)console.log(""),console.log(C.gray("Usage:")),console.log(C.dim("─".repeat(40))),console.log(J.usage);console.log("")}catch(J){if(Z.fail("Failed to fetch module info"),J instanceof Error)console.error(C.red(J.message));process.exit(1)}});import F91 from"path";var kK=b1(o8(),1);import{execSync as dh0}from"child_process";import wq from"path";async function cE(D,X=!1){let Z=Date.now(),J=[],Y=wq.join(D,"tsconfig.json");if(!await kK.default.pathExists(Y))return{name:"TypeScript",status:"skip",severity:"info",duration:Date.now()-Z,items:[],summary:"No tsconfig.json found"};let Q=wq.join(D,"package.json"),$=!1;if(await kK.default.pathExists(Q))$=!!(await kK.default.readJson(Q)).scripts?.check;let K=tQ(D),G=K==="npm"?"npm run":`${K} run`;try{let F=$?`${G} check 2>&1`:"npx tsc --noEmit 2>&1";if(!X)console.log(C.dim(` $ ${$?`${G} check`:"tsc --noEmit"}`));let W=dh0(F,{cwd:D,encoding:"utf-8",stdio:"pipe"});if(!X&&W.trim())console.log(C.dim(W));return{name:"TypeScript",status:"pass",severity:"error",duration:Date.now()-Z,items:[],summary:"No type errors"}}catch(F){let W=F.stdout||F.stderr||F.message||"";if(W.includes("This is not the tsc command")||W.includes("typescript"))return{name:"TypeScript",status:"skip",severity:"info",duration:Date.now()-Z,items:[],summary:"TypeScript not installed"};if(!X&&W.trim())console.log(C.dim(W));return J.push(...lh0(W,D)),{name:"TypeScript",status:"fail",severity:"error",duration:Date.now()-Z,items:J,summary:`${J.length} type error(s)`}}}function lh0(D,X){let Z=[],J=/^(.+)\((\d+),(\d+)\):\s*(error|warning)\s*(TS\d+):\s*(.+)$/gm,Y=/^(.+):(\d+):(\d+)\s*-\s*(error|warning)\s*(TS\d+):\s*(.+)$/gm;for(let $ of[J,Y]){let K;while((K=$.exec(D))!==null){let G=K[1].trim();Z.push({file:wq.isAbsolute(G)?wq.relative(X,G):G,line:parseInt(K[2]),column:parseInt(K[3]),code:K[5],message:K[6]})}}let Q=new Set;return Z.filter(($)=>{let K=`${$.file}:${$.line}:${$.column}:${$.message}`;if(Q.has(K))return!1;return Q.add(K),!0})}var pD=b1(o8(),1);import{execSync as ph0}from"child_process";import hK from"path";var ih0=["eslint.config.js","eslint.config.mjs","eslint.config.cjs",".eslintrc.js",".eslintrc.cjs",".eslintrc.json",".eslintrc.yml",".eslintrc.yaml",".eslintrc"];async function nh0(D){for(let Z of ih0)if(await pD.default.pathExists(hK.join(D,Z)))return!0;let X=hK.join(D,"package.json");if(await pD.default.pathExists(X))try{if((await pD.default.readJson(X)).eslintConfig)return!0}catch{}return!1}async function dE(D,X=!1,Z=!1){let J=Date.now();if(!await nh0(D))return{name:"ESLint",status:"skip",severity:"warning",duration:Date.now()-J,items:[],summary:"No ESLint config found"};let Y=hK.join(D,"package.json"),Q=!1;if(await pD.default.pathExists(Y))Q=!!(await pD.default.readJson(Y)).scripts?.lint;let $=tQ(D),K=$==="npm"?"npm run":`${$} run`,G=X?" -- --fix":"";try{let F=Q?`${K} lint${G} 2>&1`:`npx eslint .${X?" --fix":""} 2>&1`;if(!Z)console.log(C.dim(` $ ${Q?`${K} lint${G}`:`eslint .${X?" --fix":""}`}`));let W=ph0(F,{cwd:D,encoding:"utf-8",stdio:"pipe",maxBuffer:10485760});if(!Z&&W.trim())console.log(C.dim(W));return{name:"ESLint",status:"pass",severity:"warning",duration:Date.now()-J,items:[],summary:"No linting issues"}}catch(F){let W=F.stdout||F.stderr||F.message||"";if(W.includes("eslint: not found")||W.includes("'eslint' is not recognized"))return{name:"ESLint",status:"skip",severity:"info",duration:Date.now()-J,items:[],summary:"ESLint not installed"};if(!Z&&W.trim())console.log(C.dim(W));let V=oh0(W,D);if(V.length===0&&W.includes("error"))return{name:"ESLint",status:"fail",severity:"warning",duration:Date.now()-J,items:[{message:"ESLint encountered an error"}],summary:"ESLint error"};return{name:"ESLint",status:V.length>0?"fail":"pass",severity:"warning",duration:Date.now()-J,items:V,summary:V.length>0?`${V.length} linting issue(s)`:"No linting issues"}}}function oh0(D,X){let Z=[],J=/^([^\s].*\.(ts|tsx|js|jsx))$/gm,Y=/^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)?$/gm,Q=null,$=D.split(`
|
|
493
493
|
`);for(let K of $){let G=K.match(/^([^\s].*\.(ts|tsx|js|jsx))$/);if(G){Q=G[1];continue}let F=K.match(/^\s+(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)?$/);if(F&&Q)Z.push({file:hK.isAbsolute(Q)?hK.relative(X,Q):Q,line:parseInt(F[1]),column:parseInt(F[2]),code:F[5]||void 0,message:F[4].trim()})}return Z}var Sq=b1(o8(),1);import{execSync as rh0}from"child_process";import xK from"path";async function lE(D){let X=Date.now(),Z=["vite.config.ts","vite.config.js","vite.config.mjs"],J=!1;for(let Y of Z)if(await Sq.default.pathExists(xK.join(D,Y))){J=!0;break}if(!J)return{name:"Build",status:"skip",severity:"error",duration:Date.now()-X,items:[],summary:"No vite.config found"};try{rh0("npx vite build 2>&1",{cwd:D,encoding:"utf-8",stdio:"pipe",env:{...process.env,NODE_ENV:"production"}});let Y=xK.join(D,"dist");if(await Sq.default.pathExists(Y))await Sq.default.remove(Y);return{name:"Build",status:"pass",severity:"error",duration:Date.now()-X,items:[],summary:"Build succeeded"}}catch(Y){let Q=Y.stdout||Y.stderr||Y.message||"",$=sh0(Q,D);return{name:"Build",status:"fail",severity:"error",duration:Date.now()-X,items:$.length>0?$:[{message:"Build failed"}],summary:"Build failed"}}}function sh0(D,X){let Z=[],J=D.split(`
|
|
494
494
|
`),Y=[/^(.+):(\d+):(\d+):\s*(.+)$/,/\[vite\]:\s*Error:\s*(.+)/,/Error:\s*(.+?)\s+at\s+(.+):(\d+):(\d+)/,/Could not resolve ["']([^"']+)["']\s+from\s+["']([^"']+)["']/,/RollupError:\s*(.+)/];for(let $=0;$<J.length;$++){let K=J[$].trim();if(!K)continue;if(K.includes("building for production")||K.includes("transforming")||K.includes("rendering chunks")||K.includes("computing gzip")||K.startsWith("✓")||K.startsWith("vite v"))continue;for(let G of Y){let F=K.match(G);if(F){if(G.source.includes("Could not resolve"))Z.push({file:xK.relative(X,F[2]),message:`Cannot find module '${F[1]}'`,suggestion:"Check if the module is installed or the path is correct"});else if(F.length===5)Z.push({file:xK.isAbsolute(F[1]||F[2])?xK.relative(X,F[1]||F[2]):F[1]||F[2],line:parseInt(F[2]||F[3]),column:parseInt(F[3]||F[4]),message:F[4]||F[1]});else Z.push({message:F[1]||K});break}}if(Z.length===0&&(K.toLowerCase().includes("error")||K.toLowerCase().includes("failed")))Z.push({message:K})}let Q=new Set;return Z.filter(($)=>{let K=`${$.file}:${$.line}:${$.message}`;if(Q.has(K))return!1;return Q.add(K),!0})}var bS=b1(o8(),1);import sW from"path";var s41=[process.env["PROGRAMFILES(X86)"]&&sW.join(process.env["PROGRAMFILES(X86)"],"Google","Chrome","Application","chrome.exe"),process.env.PROGRAMFILES&&sW.join(process.env.PROGRAMFILES,"Google","Chrome","Application","chrome.exe"),process.env.LOCALAPPDATA&&sW.join(process.env.LOCALAPPDATA,"Google","Chrome","Application","chrome.exe"),process.env["PROGRAMFILES(X86)"]&&sW.join(process.env["PROGRAMFILES(X86)"],"Microsoft","Edge","Application","msedge.exe"),process.env.PROGRAMFILES&&sW.join(process.env.PROGRAMFILES,"Microsoft","Edge","Application","msedge.exe")].filter(Boolean),a41=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Chromium.app/Contents/MacOS/Chromium"],t41=["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/usr/bin/chromium-browser","/usr/bin/chromium","/snap/bin/chromium"];async function A$0(){let D=await e41();if(!D)return null;try{return{type:"chrome",browser:await(await Promise.resolve().then(() => (M$0(),L$0))).default.launch({executablePath:D,headless:!0,args:["--no-sandbox","--disable-setuid-sandbox","--disable-gpu"]})}}catch{return null}}async function e41(){if(process.env.CHROME_PATH){if(await bS.default.pathExists(process.env.CHROME_PATH))return process.env.CHROME_PATH}let D;switch(process.platform){case"win32":D=s41;break;case"darwin":D=a41;break;default:D=t41}for(let X of D)if(await bS.default.pathExists(X))return X;return null}async function N$0(D){try{await D.browser.close()}catch{}}async function fS(D,X,Z="/"){let J=Date.now();if(!await D91(D))return{name:"Runtime",status:"skip",severity:"error",duration:Date.now()-J,items:[{message:`No server running on port ${D}`,suggestion:"Start dev server first: npm run dev (or use --port to specify different port)"}],summary:`Skipped (no server on port ${D})`};let Q=null;try{Q=await A$0()}catch{}if(!Q)return{name:"Runtime",status:"skip",severity:"info",duration:Date.now()-J,items:[{message:"No browser available (Chrome/Chromium/Edge not found)",suggestion:"Set CHROME_PATH environment variable or install Chrome/Edge"}],summary:"Skipped (no browser)"};let $=[],{browser:K,type:G}=Q;try{let W=await K.newPage();W.on("console",(H)=>{let q=H.type();if(q==="error"||q==="warning"){let B=H.location();$.push({type:q==="warning"?"warning":"console",message:H.text(),fileName:B?.url,lineNumber:B?.lineNumber,columnNumber:B?.columnNumber})}}),W.on("pageerror",(H)=>{$.push({type:"error",message:H.message,stack:H.stack})});let V=Z.startsWith("/")?Z:`/${Z}`;await W.goto(`http://localhost:${D}${V}`,{waitUntil:"networkidle0",timeout:X}),await X91(3000);let z=await W.evaluate(()=>{return window.__earlyErrors||[]});$.push(...z)}catch(W){let V=W.message||"Unknown error";if(V.includes("timeout")||V.includes("Timeout"))$.push({type:"error",message:"Page load timeout - app may have crashed or is unresponsive"});else if(V.includes("net::ERR"))$.push({type:"error",message:`Network error: ${V}`});else $.push({type:"error",message:`Runtime check error: ${V}`})}finally{await N$0(Q)}let F=Z91($);return{name:`Runtime (${G})`,status:F.length>0?"fail":"pass",severity:"error",duration:Date.now()-J,items:F,summary:F.length>0?`${F.length} runtime error(s)`:"No runtime errors"}}async function D91(D){try{let X=await fetch(`http://localhost:${D}`,{method:"HEAD",signal:AbortSignal.timeout(3000)});return X.ok||X.status<500}catch{return!1}}function X91(D){return new Promise((X)=>setTimeout(X,D))}function Z91(D){return D.map((X)=>{let Z=[];for(let[J,Y]of Object.entries(X))if(Y!==void 0&&Y!==null)Z.push(`${J}: ${Y}`);return{message:Z.join(`
|
|
495
|
-
`)}})}function gS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${W91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)J91(Z,X);console.log(C.dim("─".repeat(40))),G91(D)}function J91(D,X){let Z=$91(D.status),J=K91(D.status,D.severity),Y=C.dim(`${D.duration}ms`);console.log(`${Z} ${J(D.name)} ${Y} ${C.dim("·")} ${C.dim(D.summary||"")}`);let Q=D.name.startsWith("Runtime"),$=X||Q?D.items:D.items.slice(0,3),K=D.items.length-$.length;for(let G of $)Y91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function Y91(D,X){let Z=X==="error"?C.red:C.yellow,J=Q91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function Q91(D){if(!D.file)return"";let X=D.file;if(D.line!==void 0){if(X+=`:${D.line}`,D.column!==void 0)X+=`:${D.column}`}return X}function $91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function K91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function G91(D){let{summary:X}=D,Z=[];if(X.passed>0)Z.push(C.green(`${X.passed} passed`));if(X.failed>0)Z.push(C.red(`${X.failed} failed`));if(X.skipped>0)Z.push(C.dim(`${X.skipped} skipped`));if(console.log(Z.join(C.dim(" · "))),X.errors>0)console.log(C.red(`${X.errors} error(s) need attention`));else if(X.warnings>0)console.log(C.yellow(`${X.warnings} warning(s) to review`));else console.log(C.green("All good!"))}function W91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var R$0=new $5("doctor").description("Analyze project for issues (TypeScript, ESLint, build, runtime)").option("--cwd <path>","Working directory").option("--port <number>","Vite dev server port","5174").option("--fix","Auto-fix ESLint issues where possible").option("--runtime","Run runtime error detection (requires running dev server)").option("--runtime-timeout <ms>","Runtime check timeout in ms","10000").option("--route <path>","Route path for runtime check","/").option("--json","Output as JSON (for CI/CD)").option("-v, --verbose","Show all details including all errors").option("-q, --quiet","Hide command output logs (only show summary)").option("--no-typecheck","Skip TypeScript type checking").option("--no-lint","Skip ESLint checking").option("--no-build","Skip Vite build checking").action(async(D)=>{let X=D.cwd||process.cwd(),Z=parseInt(D.port||"5173"),J=parseInt(D.runtimeTimeout||"10000"),Y=D.route||"/",Q=Date.now(),$=D.quiet||D.json||!1;if(!await T2(X)&&!D.json)console.log(C.yellow("Warning: No promake.json found. Running checks anyway..."));let G=D.json?null:E2("Running health checks...").start(),F=[];if(D.typecheck!==!1)F.push(()=>cE(X,$));if(D.lint!==!1)F.push(()=>dE(X,D.fix,$));if(D.build!==!1)F.push(()=>lE(X));if(D.runtime)F.push(()=>fS(Z,J,Y));let W=[];try{if(W=await V91(F,1),G)G.stop()}catch(z){if(G)G.fail("Health check failed");if(!D.json)console.error(C.red(z.message));process.exit(1)}let V={project:F91.basename(X),timestamp:new Date().toISOString(),duration:Date.now()-Q,checks:W,summary:{passed:W.filter((z)=>z.status==="pass").length,failed:W.filter((z)=>z.status==="fail").length,skipped:W.filter((z)=>z.status==="skip").length,errors:W.filter((z)=>z.status==="fail"&&z.severity==="error").reduce((z,H)=>z+H.items.length,0),warnings:W.filter((z)=>z.status==="fail"&&z.severity==="warning").reduce((z,H)=>z+H.items.length,0)}};if(D.json)console.log(JSON.stringify(V,null,2));else gS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function V91(D,X){let Z=[],J=0;async function Y(){let $=J++;if($>=D.length)return;Z[$]=await D[$](),await Y()}let Q=Array(Math.min(X,D.length)).fill(null).map(()=>Y());return await Promise.all(Q),Z}var E$0={name:"@promakeai/cli",version:"0.2.
|
|
495
|
+
`)}})}function gS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${W91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)J91(Z,X);console.log(C.dim("─".repeat(40))),G91(D)}function J91(D,X){let Z=$91(D.status),J=K91(D.status,D.severity),Y=C.dim(`${D.duration}ms`);console.log(`${Z} ${J(D.name)} ${Y} ${C.dim("·")} ${C.dim(D.summary||"")}`);let Q=D.name.startsWith("Runtime"),$=X||Q?D.items:D.items.slice(0,3),K=D.items.length-$.length;for(let G of $)Y91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function Y91(D,X){let Z=X==="error"?C.red:C.yellow,J=Q91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function Q91(D){if(!D.file)return"";let X=D.file;if(D.line!==void 0){if(X+=`:${D.line}`,D.column!==void 0)X+=`:${D.column}`}return X}function $91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function K91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function G91(D){let{summary:X}=D,Z=[];if(X.passed>0)Z.push(C.green(`${X.passed} passed`));if(X.failed>0)Z.push(C.red(`${X.failed} failed`));if(X.skipped>0)Z.push(C.dim(`${X.skipped} skipped`));if(console.log(Z.join(C.dim(" · "))),X.errors>0)console.log(C.red(`${X.errors} error(s) need attention`));else if(X.warnings>0)console.log(C.yellow(`${X.warnings} warning(s) to review`));else console.log(C.green("All good!"))}function W91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var R$0=new $5("doctor").description("Analyze project for issues (TypeScript, ESLint, build, runtime)").option("--cwd <path>","Working directory").option("--port <number>","Vite dev server port","5174").option("--fix","Auto-fix ESLint issues where possible").option("--runtime","Run runtime error detection (requires running dev server)").option("--runtime-timeout <ms>","Runtime check timeout in ms","10000").option("--route <path>","Route path for runtime check","/").option("--json","Output as JSON (for CI/CD)").option("-v, --verbose","Show all details including all errors").option("-q, --quiet","Hide command output logs (only show summary)").option("--no-typecheck","Skip TypeScript type checking").option("--no-lint","Skip ESLint checking").option("--no-build","Skip Vite build checking").action(async(D)=>{let X=D.cwd||process.cwd(),Z=parseInt(D.port||"5173"),J=parseInt(D.runtimeTimeout||"10000"),Y=D.route||"/",Q=Date.now(),$=D.quiet||D.json||!1;if(!await T2(X)&&!D.json)console.log(C.yellow("Warning: No promake.json found. Running checks anyway..."));let G=D.json?null:E2("Running health checks...").start(),F=[];if(D.typecheck!==!1)F.push(()=>cE(X,$));if(D.lint!==!1)F.push(()=>dE(X,D.fix,$));if(D.build!==!1)F.push(()=>lE(X));if(D.runtime)F.push(()=>fS(Z,J,Y));let W=[];try{if(W=await V91(F,1),G)G.stop()}catch(z){if(G)G.fail("Health check failed");if(!D.json)console.error(C.red(z.message));process.exit(1)}let V={project:F91.basename(X),timestamp:new Date().toISOString(),duration:Date.now()-Q,checks:W,summary:{passed:W.filter((z)=>z.status==="pass").length,failed:W.filter((z)=>z.status==="fail").length,skipped:W.filter((z)=>z.status==="skip").length,errors:W.filter((z)=>z.status==="fail"&&z.severity==="error").reduce((z,H)=>z+H.items.length,0),warnings:W.filter((z)=>z.status==="fail"&&z.severity==="warning").reduce((z,H)=>z+H.items.length,0)}};if(D.json)console.log(JSON.stringify(V,null,2));else gS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function V91(D,X){let Z=[],J=0;async function Y(){let $=J++;if($>=D.length)return;Z[$]=await D[$](),await Y()}let Q=Array(Math.min(X,D.length)).fill(null).map(()=>Y());return await Promise.all(Q),Z}var E$0={name:"@promakeai/cli",version:"0.2.12",type:"module",bin:{promake:"dist/index.js"},files:["dist/index.js","dist/registry","template"],scripts:{dev:"bun run src/index.ts","dev:app":"cd dev && bun run dev","dev:populate":"bun run scripts/populate-dev.ts","dev:fresh":"bun run dev:populate && bun run dev:app","playground:create":"rm -rf playground && bun run dev -- create playground --template empty --pm bun","playground:reset":"bun run playground:create","playground:add":"cd playground && bun run ../src/index.ts add","playground:ecommerce":"rm -rf playground && bun run dev -- create playground --template ecommerce --pm bun",build:"bun run build:cli && bun run build:registry","build:cli":"bun build src/index.ts --outdir dist --target node --minify","build:registry":"bun run scripts/build-registry.ts",typecheck:"tsc --noEmit",prepublishOnly:"bun run build",test:"bun test","test:watch":"bun test --watch","test:coverage":"bun test --coverage",release:"bun run build && npm publish --access public"},dependencies:{"adm-zip":"^0.5.16",archiver:"^7.0.1",chalk:"^5.3.0",commander:"^12.1.0",culori:"^4.0.2","fs-extra":"^11.2.0",glob:"^11.0.0",ora:"^8.1.1",prompts:"^2.4.2","puppeteer-core":"^24.36.0"},devDependencies:{"@types/archiver":"^7.0.0","@types/bun":"^1.1.14","@types/culori":"^4.0.1","@types/fs-extra":"^11.0.4","@types/node":"^22.10.2","@types/prompts":"^2.4.9",typescript:"^5.7.2"}};var C8=new $5;C8.name("promake").description(`Modular React template CLI - Build React apps with pre-built components
|
|
496
496
|
|
|
497
497
|
Quick Start:
|
|
498
498
|
$ promake create my-app
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"path": "about-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/about-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"About Us\",\r\n \"subtitle\": \"Tell your brand's story, vision, and value in a few sentences.\",\r\n \"storyTitle\": \"Our Story\",\r\n \"storyP1\": \"Tell your brand's origins, motivation, and current position. Include key turning points.\",\r\n \"storyP2\": \"Continue your story. Deepen the journey mentioned in the first paragraph.\",\r\n \"missionTitle\": \"Our Mission\",\r\n \"missionDesc\": \"Describe your brand's core purpose and user value.\",\r\n \"valuesTitle\": \"Our Values\",\r\n \"valuesDesc\": \"Share the core values that define your brand.\",\r\n \"teamTitle\": \"Our Team\",\r\n \"teamDesc\": \"Describe your team structure, culture, and collaboration.\",\r\n \"qualityTitle\": \"Quality First\",\r\n \"qualityDesc\": \"Emphasize quality standards and user experience focus.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Happy Customers\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Projects Completed\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Years Experience\",\r\n \"satisfactionValue\": \"99%\",\r\n \"satisfactionLabel\": \"Client Satisfaction\",\r\n \"ctaTitle\": \"Ready to Work Together?\",\r\n \"ctaDesc\": \"Update this CTA based on your goals.\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "about-page/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/about-page/lang/tr.json",
|
|
37
|
-
"content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"subtitle\": \"
|
|
37
|
+
"content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"subtitle\": \"Markanızın hikayesini, vizyonunu ve değerini birkaç cümleyle anlatın.\",\r\n \"storyTitle\": \"Hikayemiz\",\r\n \"storyP1\": \"Markanızın doğuşunu, motivasyonunu ve bugünkü konumunu anlatın. Dönüm noktalarını ekleyin.\",\r\n \"storyP2\": \"Hikayenizin devamı. İlk paragraftaki yolculuğu derinleştirin.\",\r\n \"missionTitle\": \"Misyonumuz\",\r\n \"missionDesc\": \"Markanızın temel amacını ve kullanıcı değerini anlatın.\",\r\n \"valuesTitle\": \"Değerlerimiz\",\r\n \"valuesDesc\": \"Markanızı tanımlayan temel değerleri paylaşın.\",\r\n \"teamTitle\": \"Ekibimiz\",\r\n \"teamDesc\": \"Ekip yapınızı, kültürünüzü ve işbirliğinizi anlatın.\",\r\n \"qualityTitle\": \"Önce Kalite\",\r\n \"qualityDesc\": \"Kalite standartlarınızı ve kullanıcı deneyimi odağınızı vurgulayın.\",\r\n \"customersValue\": \"500+\",\r\n \"customersLabel\": \"Mutlu Müşteri\",\r\n \"projectsValue\": \"1000+\",\r\n \"projectsLabel\": \"Tamamlanan Proje\",\r\n \"experienceValue\": \"10+\",\r\n \"experienceLabel\": \"Yıllık Deneyim\",\r\n \"satisfactionValue\": \"%99\",\r\n \"satisfactionLabel\": \"Müşteri Memnuniyeti\",\r\n \"ctaTitle\": \"Birlikte Çalışmaya Hazır mısınız?\",\r\n \"ctaDesc\": \"Bu CTA'yı hedeflerinize göre güncelleyin.\"\r\n}"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "about-section/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/about-section/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"title\": \"About Us\",\r\n \"description\": \"
|
|
25
|
+
"content": "{\r\n \"title\": \"About Us\",\r\n \"description\": \"We're a team of passionate professionals dedicated to delivering exceptional results for our clients.\",\r\n \"mainImageAlt\": \"Our team\",\r\n \"secondaryImageAlt\": \"Our office\",\r\n \"cardTitle\": \"Our Mission\",\r\n \"cardDescription\": \"To empower businesses with innovative solutions that drive growth and success.\",\r\n \"cardButton\": \"Learn More\",\r\n \"companiesTitle\": \"Trusted by Leading Brands\",\r\n \"statsTitle\": \"Our Achievements\",\r\n \"statsDescription\": \"Numbers that showcase our commitment to excellence.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Happy Clients\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Projects Completed\",\r\n \"stat3Value\": \"99%\",\r\n \"stat3Label\": \"Satisfaction Rate\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Years Experience\"\r\n}"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "about-section/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/about-section/lang/tr.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Hakkımızda\",\r\n \"description\": \"Müşterilerimize olağanüstü sonuçlar sunmaya kendini adamış, tutkulu profesyonellerden oluşan bir ekibiz.\",\r\n \"mainImageAlt\": \"Ekibimiz\",\r\n \"secondaryImageAlt\": \"Ofisimiz\",\r\n \"cardTitle\": \"Misyonumuz\",\r\n \"cardDescription\": \"İşletmeleri büyüme ve başarı sağlayan yenilikçi çözümlerle güçlendirmek.\",\r\n \"cardButton\": \"Daha Fazla\",\r\n \"companiesTitle\": \"Önde Gelen Markalar Tarafından Güvenilir\",\r\n \"statsTitle\": \"Başarılarımız\",\r\n \"statsDescription\": \"Mükemmeliyete olan bağlılığımızı gösteren rakamlar.\",\r\n \"stat1Value\": \"500+\",\r\n \"stat1Label\": \"Mutlu Müşteri\",\r\n \"stat2Value\": \"1000+\",\r\n \"stat2Label\": \"Tamamlanan Proje\",\r\n \"stat3Value\": \"%99\",\r\n \"stat3Label\": \"Memnuniyet Oranı\",\r\n \"stat4Value\": \"15+\",\r\n \"stat4Label\": \"Yıllık Deneyim\"\r\n}"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "announcement-bar/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/announcement-bar/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"message\": \"
|
|
28
|
+
"content": "{\r\n \"message\": \"Important announcement: Limited time offer - Learn more →\",\r\n \"linkText\": \"Learn more\",\r\n \"dismiss\": \"Dismiss\"\r\n}"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "announcement-bar/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/announcement-bar/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"message\": \"
|
|
34
|
+
"content": "{\r\n \"message\": \"Önemli duyuru: Sınırlı süreli kampanya - Detaylar →\",\r\n \"linkText\": \"Daha fazla\",\r\n \"dismiss\": \"Kapat\"\r\n}"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"path": "blog-list-page/blog-list-page.tsx",
|
|
25
25
|
"type": "registry:page",
|
|
26
26
|
"target": "$modules$/blog-list-page/blog-list-page.tsx",
|
|
27
|
-
"content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories, type BlogCategory } from \"@/modules/blog-core\";\n\ninterface FilterSectionProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: (key: string, options?: any) => string;\n searchTerm: string;\n setSearchTerm: (term: string) => void;\n categories: BlogCategory[];\n selectedCategories: string[];\n handleCategoryChange: (slug: string, checked: boolean) => void;\n allTags: string[];\n selectedTags: string[];\n handleTagChange: (tag: string, checked: boolean) => void;\n clearFilters: () => void;\n}\n\nfunction FilterSection({\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n}: FilterSectionProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n}\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const filterProps: FilterSectionProps = {\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n };\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection {...filterProps} />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection {...filterProps} />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
|
|
27
|
+
"content": "import { useState, useEffect } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { useTranslation } from \"react-i18next\";\nimport { Layout } from \"@/components/Layout\";\nimport { Search, Filter } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { PostCard } from \"@/modules/post-card/post-card\";\nimport { usePosts, useBlogCategories, type BlogCategory } from \"@/modules/blog-core\";\n\ninterface FilterSectionProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n searchTerm: string;\n setSearchTerm: (term: string) => void;\n categories: BlogCategory[];\n selectedCategories: string[];\n handleCategoryChange: (slug: string, checked: boolean) => void;\n allTags: string[];\n selectedTags: string[];\n handleTagChange: (tag: string, checked: boolean) => void;\n clearFilters: () => void;\n}\n\nfunction FilterSection({\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n}: FilterSectionProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-3 flex items-center gap-2\">\n <Search className=\"h-4 w-4\" />\n {t(\"search\")}\n </h3>\n <Input\n placeholder={t(\"searchPlaceholder\")}\n value={searchTerm}\n onChange={(e) => setSearchTerm(e.target.value)}\n />\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"categories\")}</h3>\n <div className=\"space-y-2\">\n {categories.map((category) => (\n <div key={category.slug} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`category-${category.slug}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n />\n <label\n htmlFor={`category-${category.slug}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n {allTags.length > 0 && (\n <div>\n <h3 className=\"font-semibold mb-3\">{t(\"tags\")}</h3>\n <div className=\"space-y-2 max-h-48 overflow-y-auto\">\n {allTags.slice(0, 20).map((tag) => (\n <div key={tag} className=\"flex items-center space-x-2\">\n <Checkbox\n id={`tag-${tag}`}\n checked={selectedTags.includes(tag)}\n onCheckedChange={(checked) =>\n handleTagChange(tag, checked as boolean)\n }\n />\n <label\n htmlFor={`tag-${tag}`}\n className=\"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer\"\n >\n {tag}\n </label>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {(searchTerm ||\n selectedCategories.length > 0 ||\n selectedTags.length > 0) && (\n <Button variant=\"outline\" onClick={clearFilters} className=\"w-full\">\n {t(\"clearFilters\")}\n </Button>\n )}\n </div>\n );\n}\n\nexport function BlogListPage() {\n const { t } = useTranslation(\"blog-list-page\");\n usePageTitle({ title: t(\"title\") });\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [searchTerm, setSearchTerm] = useState(\n searchParams.get(\"search\") || \"\"\n );\n const [selectedCategories, setSelectedCategories] = useState<string[]>(\n searchParams.get(\"categories\")?.split(\",\").filter(Boolean) || []\n );\n const [selectedTags, setSelectedTags] = useState<string[]>(\n searchParams.get(\"tags\")?.split(\",\").filter(Boolean) || []\n );\n const [sortBy, setSortBy] = useState(searchParams.get(\"sort\") || \"newest\");\n const [viewMode, _setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n\n const { posts, loading, error } = usePosts();\n const { categories } = useBlogCategories();\n\n const filteredPosts = posts.filter((post) => {\n if (searchTerm) {\n const searchLower = searchTerm.toLowerCase();\n if (\n !post.title.toLowerCase().includes(searchLower) &&\n !post.excerpt.toLowerCase().includes(searchLower) &&\n !post.content.toLowerCase().includes(searchLower)\n ) {\n return false;\n }\n }\n\n if (selectedCategories.length > 0) {\n const hasMatchingCategory = selectedCategories.some(\n (categorySlug) =>\n post.category === categorySlug ||\n post.categories?.some((cat) => cat.slug === categorySlug)\n );\n if (!hasMatchingCategory) return false;\n }\n\n if (selectedTags.length > 0) {\n const hasMatchingTag = selectedTags.some((tag) =>\n post.tags.includes(tag)\n );\n if (!hasMatchingTag) return false;\n }\n\n return true;\n });\n\n const sortedPosts = [...filteredPosts].sort((a, b) => {\n switch (sortBy) {\n case \"oldest\":\n return (\n new Date(a.published_at).getTime() -\n new Date(b.published_at).getTime()\n );\n case \"popular\":\n return (b.view_count || 0) - (a.view_count || 0);\n case \"reading-time\":\n return (a.read_time || 0) - (b.read_time || 0);\n case \"newest\":\n default:\n return (\n new Date(b.published_at).getTime() -\n new Date(a.published_at).getTime()\n );\n }\n });\n\n useEffect(() => {\n const params = new URLSearchParams();\n if (searchTerm) params.set(\"search\", searchTerm);\n if (selectedCategories.length)\n params.set(\"categories\", selectedCategories.join(\",\"));\n if (selectedTags.length) params.set(\"tags\", selectedTags.join(\",\"));\n if (sortBy !== \"newest\") params.set(\"sort\", sortBy);\n\n setSearchParams(params);\n }, [searchTerm, selectedCategories, selectedTags, sortBy, setSearchParams]);\n\n const handleCategoryChange = (categorySlug: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories([...selectedCategories, categorySlug]);\n } else {\n setSelectedCategories(\n selectedCategories.filter((c) => c !== categorySlug)\n );\n }\n };\n\n const handleTagChange = (tag: string, checked: boolean) => {\n if (checked) {\n setSelectedTags([...selectedTags, tag]);\n } else {\n setSelectedTags(selectedTags.filter((t) => t !== tag));\n }\n };\n\n const allTags = Array.from(new Set(posts.flatMap((post) => post.tags)));\n\n const clearFilters = () => {\n setSearchTerm(\"\");\n setSelectedCategories([]);\n setSelectedTags([]);\n setSortBy(\"newest\");\n };\n\n const filterProps: FilterSectionProps = {\n t,\n searchTerm,\n setSearchTerm,\n categories,\n selectedCategories,\n handleCategoryChange,\n allTags,\n selectedTags,\n handleTagChange,\n clearFilters,\n };\n\n if (loading) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <div className=\"animate-pulse space-y-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"h-48 bg-muted rounded-lg\"></div>\n ))}\n </div>\n </div>\n </Layout>\n );\n }\n\n if (error) {\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8 text-center\">\n <p className=\"text-destructive\">{t(\"error\")}</p>\n </div>\n </Layout>\n );\n }\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"flex flex-col lg:flex-row justify-between items-start lg:items-center gap-4 mb-8\">\n <div>\n <h1 className=\"text-3xl font-bold mb-2\">{t(\"title\")}</h1>\n <p className=\"text-muted-foreground\">{t(\"subtitle\")}</p>\n </div>\n\n <div className=\"flex items-center gap-4\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-[180px]\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"newest\">{t(\"sortNewest\")}</SelectItem>\n <SelectItem value=\"oldest\">{t(\"sortOldest\")}</SelectItem>\n <SelectItem value=\"popular\">{t(\"sortPopular\")}</SelectItem>\n <SelectItem value=\"reading-time\">\n {t(\"sortReadingTime\")}\n </SelectItem>\n </SelectContent>\n </Select>\n\n <Sheet>\n <SheetTrigger asChild>\n <Button variant=\"outline\" size=\"sm\" className=\"lg:hidden\">\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent>\n <SheetHeader>\n <SheetTitle>{t(\"filters\")}</SheetTitle>\n <SheetDescription>{t(\"filterDescription\")}</SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSection {...filterProps} />\n </div>\n </SheetContent>\n </Sheet>\n </div>\n </FadeIn>\n\n <div className=\"flex flex-col lg:flex-row gap-8\">\n <div className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-4\">\n <FilterSection {...filterProps} />\n </div>\n </div>\n\n <div className=\"flex-1\">\n <div className=\"flex items-center justify-between mb-6\">\n <p className=\"text-sm text-muted-foreground\">\n {t(\"showing\")} {sortedPosts.length} {t(\"of\")} {posts.length}{\" \"}\n {t(\"posts\")}\n {searchTerm && (\n <span className=\"ml-1\">\n {t(\"for\")} \"<strong>{searchTerm}</strong>\"\n </span>\n )}\n </p>\n </div>\n\n {sortedPosts.length > 0 ? (\n <div\n className={`grid gap-6 ${\n viewMode === \"grid\"\n ? \"grid-cols-1 md:grid-cols-2 xl:grid-cols-3\"\n : \"grid-cols-1\"\n }`}\n >\n {sortedPosts.map((post) => (\n <PostCard key={post.id} post={post} layout={viewMode} />\n ))}\n </div>\n ) : (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground mb-4\">\n {t(\"noPostsFound\")}\n </p>\n <Button onClick={clearFilters} variant=\"outline\">\n {t(\"clearFilters\")}\n </Button>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default BlogListPage;\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "blog-list-page/lang/en.json",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"path": "blog-section/lang/en.json",
|
|
28
28
|
"type": "registry:lang",
|
|
29
29
|
"target": "$modules$/blog-section/lang/en.json",
|
|
30
|
-
"content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"
|
|
30
|
+
"content": "{\r\n \"tagline\": \"Latest Updates\",\r\n \"title\": \"From Our Blog\",\r\n \"subtitle\": \"Insights, tutorials, and stories to help you succeed.\",\r\n \"viewAll\": \"View all articles\",\r\n \"readMore\": \"Read more\",\r\n \"post1Title\": \"Getting Started: A Complete Guide\",\r\n \"post1Summary\": \"Learn the essential steps to launch your project successfully and avoid common pitfalls.\",\r\n \"post1Category\": \"Tutorial\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"Jan 15, 2024\",\r\n \"post2Title\": \"10 Best Practices You Need to Know\",\r\n \"post2Summary\": \"Discover proven strategies that will help you work smarter and achieve better results.\",\r\n \"post2Category\": \"Best Practices\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"Jan 10, 2024\",\r\n \"post3Title\": \"Design Trends That Matter in 2024\",\r\n \"post3Summary\": \"Stay ahead of the curve with these emerging design trends shaping the industry.\",\r\n \"post3Category\": \"Design\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"Jan 5, 2024\"\r\n}"
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
33
|
"path": "blog-section/lang/tr.json",
|
|
34
34
|
"type": "registry:lang",
|
|
35
35
|
"target": "$modules$/blog-section/lang/tr.json",
|
|
36
|
-
"content": "{\r\n \"tagline\": \"Son Güncellemeler\",\r\n \"title\": \"Blogumuzdan\",\r\n \"subtitle\": \"
|
|
36
|
+
"content": "{\r\n \"tagline\": \"Son Güncellemeler\",\r\n \"title\": \"Blogumuzdan\",\r\n \"subtitle\": \"Ekibimizden içgörüler, ipuçları ve hikayeleri keşfedin.\",\r\n \"viewAll\": \"Tüm makaleleri görüntüle\",\r\n \"readMore\": \"Devamını oku\",\r\n \"post1Title\": \"Başlangıç Rehberi\",\r\n \"post1Summary\": \"Kapsamlı rehberimizle hızlı bir şekilde başlamak için temel adımları öğrenin.\",\r\n \"post1Category\": \"Eğitim\",\r\n \"post1Author\": \"Sarah Chen\",\r\n \"post1Date\": \"15 Ocak 2024\",\r\n \"post2Title\": \"Başarı İçin En İyi Uygulamalar\",\r\n \"post2Summary\": \"Daha iyi sonuçlar elde etmenize yardımcı olan kanıtlanmış stratejileri keşfedin.\",\r\n \"post2Category\": \"En İyi Uygulamalar\",\r\n \"post2Author\": \"Michael Park\",\r\n \"post2Date\": \"10 Ocak 2024\",\r\n \"post3Title\": \"2024 Tasarım Trendleri\",\r\n \"post3Summary\": \"En son tasarım trendlerini ve projelerinizde nasıl uygulayacağınızı keşfedin.\",\r\n \"post3Category\": \"Tasarım\",\r\n \"post3Author\": \"Emily Davis\",\r\n \"post3Date\": \"5 Ocak 2024\"\r\n}"
|
|
37
37
|
}
|
|
38
38
|
],
|
|
39
39
|
"exports": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"path": "coming-soon-page-minimal/lang/en.json",
|
|
27
27
|
"type": "registry:lang",
|
|
28
28
|
"target": "$modules$/coming-soon-page-minimal/lang/en.json",
|
|
29
|
-
"content": "{\r\n \"title\": \"Coming Soon\",\r\n \"daysLeft\": \"days until launch\",\r\n \"heading\": \"Coming Soon\",\r\n \"description\": \"
|
|
29
|
+
"content": "{\r\n \"title\": \"Coming Soon\",\r\n \"daysLeft\": \"days until launch\",\r\n \"heading\": \"Coming Soon\",\r\n \"description\": \"We're crafting something beautiful. Stay tuned!\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"success\": \"We'll be in touch!\"\r\n}"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "coming-soon-page-minimal/lang/tr.json",
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"path": "coming-soon-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/coming-soon-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Coming Soon\",\r\n \"heading\": \"Something Amazing is Coming\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Coming Soon\",\r\n \"heading\": \"Something Amazing is Coming\",\r\n \"description\": \"Build anticipation for your launch. Explain what's coming and why it's exciting.\",\r\n \"days\": \"Days\",\r\n \"hours\": \"Hours\",\r\n \"minutes\": \"Minutes\",\r\n \"seconds\": \"Seconds\",\r\n \"emailPlaceholder\": \"Enter your email\",\r\n \"notify\": \"Notify Me\",\r\n \"success\": \"Thank you! We'll notify you when we launch.\",\r\n \"followUs\": \"Follow us for updates\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "coming-soon-page/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/coming-soon-page/lang/tr.json",
|
|
37
|
-
"content": "{\r\n \"title\": \"Yakında\",\r\n \"heading\": \"Harika Bir Şey Geliyor\",\r\n \"description\": \"
|
|
37
|
+
"content": "{\r\n \"title\": \"Yakında\",\r\n \"heading\": \"Harika Bir Şey Geliyor\",\r\n \"description\": \"Lansmanınız için heyecan yaratın. Nelerin geleceğini ve neden heyecan verici olduğunu anlatın.\",\r\n \"days\": \"Gün\",\r\n \"hours\": \"Saat\",\r\n \"minutes\": \"Dakika\",\r\n \"seconds\": \"Saniye\",\r\n \"emailPlaceholder\": \"E-posta adresiniz\",\r\n \"notify\": \"Beni Bilgilendir\",\r\n \"success\": \"Teşekkürler! Lansman yapıldığında sizi bilgilendireceğiz.\",\r\n \"followUs\": \"Güncellemeler için bizi takip edin\"\r\n}"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"path": "contact-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/contact-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Contact Us\",\r\n \"getInTouch\": \"Get in Touch\",\r\n \"emailUs\": \"Email Us\",\r\n \"callUs\": \"Call Us\",\r\n \"visitUs\": \"Visit Us\",\r\n \"businessHours\": \"Hours\",\r\n \"sendMessage\": \"Send us a Message\",\r\n \"formNotAvailable\": \"Form is not available at the moment.\",\r\n \"fullName\": \"Full Name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"subject\": \"Subject\",\r\n \"message\": \"Message\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Thank you for your message! We will get back to you soon.\",\r\n \"error\": \"Failed to send message. Please try again later.\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Contact Us\",\r\n \"getInTouch\": \"Get in Touch\",\r\n \"emailUs\": \"Email Us\",\r\n \"callUs\": \"Call Us\",\r\n \"visitUs\": \"Visit Us\",\r\n \"businessHours\": \"Hours\",\r\n \"sendMessage\": \"Send us a Message\",\r\n \"formNotAvailable\": \"Form is not available at the moment.\",\r\n \"fullName\": \"Full Name\",\r\n \"emailAddress\": \"Email Address\",\r\n \"phoneNumber\": \"Phone Number\",\r\n \"subject\": \"Subject\",\r\n \"message\": \"Message\",\r\n \"submit\": \"Send Message\",\r\n \"sending\": \"Sending...\",\r\n \"success\": \"Thank you for your message! We will get back to you soon.\",\r\n \"error\": \"Failed to send message. Please try again later.\",\r\n \"description\": \"Have a question or need support? We're here to help and typically respond within 24 hours.\",\r\n \"email\": \"Email\",\r\n \"phone\": \"Phone\",\r\n \"address\": \"Address\",\r\n \"fullNamePlaceholder\": \"Your full name\",\r\n \"emailPlaceholder\": \"your@email.com\",\r\n \"phonePlaceholder\": \"+1 (555) 123-4567\",\r\n \"subjectPlaceholder\": \"What is this regarding?\",\r\n \"messagePlaceholder\": \"Tell us how we can help you...\",\r\n \"loading\": \"Loading contact information...\",\r\n \"emailResponse\": \"We typically respond within 24 hours\",\r\n \"needSupport\": \"Need Support?\",\r\n \"supportDescription\": \"For technical support or general inquiries, contact our dedicated support team:\",\r\n \"supportEmail\": \"Support Email\",\r\n \"monday_friday\": \"Monday - Friday\",\r\n \"saturday\": \"Saturday\",\r\n \"sunday\": \"Sunday\",\r\n \"closed\": \"Closed\",\r\n \"am\": \"AM\",\r\n \"pm\": \"PM\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "contact-page/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/contact-page/lang/tr.json",
|
|
37
|
-
"content": "{\r\n \"title\": \"İletişim\",\r\n \"getInTouch\": \"Bize Ulaşın\",\r\n \"emailUs\": \"E-posta Gönderin\",\r\n \"callUs\": \"Bizi Arayın\",\r\n \"visitUs\": \"Bizi Ziyaret Edin\",\r\n \"businessHours\": \"Çalışma Saatleri\",\r\n \"sendMessage\": \"Bize Mesaj Gönderin\",\r\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"subject\": \"Konu\",\r\n \"message\": \"Mesaj\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\r\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"description\": \"
|
|
37
|
+
"content": "{\r\n \"title\": \"İletişim\",\r\n \"getInTouch\": \"Bize Ulaşın\",\r\n \"emailUs\": \"E-posta Gönderin\",\r\n \"callUs\": \"Bizi Arayın\",\r\n \"visitUs\": \"Bizi Ziyaret Edin\",\r\n \"businessHours\": \"Çalışma Saatleri\",\r\n \"sendMessage\": \"Bize Mesaj Gönderin\",\r\n \"formNotAvailable\": \"Form şu anda kullanılamıyor.\",\r\n \"fullName\": \"Ad Soyad\",\r\n \"emailAddress\": \"E-posta Adresi\",\r\n \"phoneNumber\": \"Telefon Numarası\",\r\n \"subject\": \"Konu\",\r\n \"message\": \"Mesaj\",\r\n \"submit\": \"Mesaj Gönder\",\r\n \"sending\": \"Gönderiliyor...\",\r\n \"success\": \"Mesajınız için teşekkürler! En kısa sürede size dönüş yapacağız.\",\r\n \"error\": \"Mesaj gönderilemedi. Lütfen tekrar deneyin.\",\r\n \"description\": \"İletişim seçenekleri ve müsaitlik bilgisi. Yanıt süreleri ve soru türlerini ekleyin.\",\r\n \"email\": \"E-posta\",\r\n \"phone\": \"Telefon\",\r\n \"address\": \"Adres\",\r\n \"fullNamePlaceholder\": \"Adınız ve soyadınız\",\r\n \"emailPlaceholder\": \"eposta@adresiniz.com\",\r\n \"phonePlaceholder\": \"+90 5XX XXX XX XX\",\r\n \"subjectPlaceholder\": \"Konu nedir?\",\r\n \"messagePlaceholder\": \"Size nasıl yardımcı olabiliriz...\",\r\n \"loading\": \"İletişim bilgileri yükleniyor...\",\r\n \"emailResponse\": \"Genellikle 24 saat içinde yanıt veririz\",\r\n \"needSupport\": \"Desteğe İhtiyacınız mı Var?\",\r\n \"supportDescription\": \"Teknik destek veya sipariş sorularınız için özel destek ekibimizle iletişime geçin:\",\r\n \"supportEmail\": \"Destek E-postası\",\r\n \"monday_friday\": \"Pazartesi - Cuma\",\r\n \"saturday\": \"Cumartesi\",\r\n \"sunday\": \"Pazar\",\r\n \"closed\": \"Kapalı\",\r\n \"am\": \"\",\r\n \"pm\": \"\"\r\n}"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "cta-section/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/cta-section/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"title\": \"Ready to Start Your Project?\",\r\n \"description\": \"
|
|
25
|
+
"content": "{\r\n \"title\": \"Ready to Start Your Project?\",\r\n \"description\": \"Join thousands of satisfied customers. Start your free trial today and see the difference.\",\r\n \"primaryButton\": \"Get Free Quote\",\r\n \"secondaryButton\": \"Learn About Us\"\r\n}"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "cta-section/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/cta-section/lang/tr.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Projenizi Başlatmaya Hazır mısınız?\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Projenizi Başlatmaya Hazır mısınız?\",\r\n \"description\": \"Binlerce memnun müşteriye katılın. Ücretsiz denemenizi bugün başlatın ve farkı görün.\",\r\n \"primaryButton\": \"Ücretsiz Teklif Al\",\r\n \"secondaryButton\": \"Hakkımızda\"\r\n}"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"path": "faq-categorized/lang/en.json",
|
|
25
25
|
"type": "registry:lang",
|
|
26
26
|
"target": "$modules$/faq-categorized/lang/en.json",
|
|
27
|
-
"content": "{\r\n \"title\": \"Frequently Asked Questions\",\r\n \"subtitle\": \"This introductory text helps visitors navigate your categorized FAQs. Explain how the categories are organized, encourage visitors to explore relevant sections, or mention that you're available for additional questions. Use Promake to create a helpful guide to your FAQ structure.\",\r\n \"generalTitle\": \"General\",\r\n \"general1Q\": \"Replace this with your first general question\",\r\n \"general1A\": \"Provide a comprehensive answer to this general question. Include key information that helps visitors understand your offering at a high level. Use Promake to customize with specific details that address common initial questions about what you do and how it works.\",\r\n \"general2Q\": \"This is a placeholder question that needs customization\",\r\n \"general2A\": \"Give a clear, informative answer that provides value to visitors with general questions. Be thorough but accessible. Use Promake to add information that helps newcomers understand your offering and its benefits.\",\r\n \"general3Q\": \"Customize this question for your FAQ section\",\r\n \"general3A\": \"Offer a detailed response that addresses this general aspect of your offering. Include relevant context and examples. Use Promake to ensure this answer helps visitors understand the fundamentals of what you provide.\"
|
|
27
|
+
"content": "{\r\n \"title\": \"Frequently Asked Questions\",\r\n \"subtitle\": \"This introductory text helps visitors navigate your categorized FAQs. Explain how the categories are organized, encourage visitors to explore relevant sections, or mention that you're available for additional questions. Use Promake to create a helpful guide to your FAQ structure.\",\r\n \"generalTitle\": \"General\",\r\n \"general1Q\": \"Replace this with your first general question\",\r\n \"general1A\": \"Provide a comprehensive answer to this general question. Include key information that helps visitors understand your offering at a high level. Use Promake to customize with specific details that address common initial questions about what you do and how it works.\",\r\n \"general2Q\": \"This is a placeholder question that needs customization\",\r\n \"general2A\": \"Give a clear, informative answer that provides value to visitors with general questions. Be thorough but accessible. Use Promake to add information that helps newcomers understand your offering and its benefits.\",\r\n \"general3Q\": \"Customize this question for your FAQ section\",\r\n \"general3A\": \"Offer a detailed response that addresses this general aspect of your offering. Include relevant context and examples. Use Promake to ensure this answer helps visitors understand the fundamentals of what you provide.\",\r\n \"billingTitle\": \"Billing\",\r\n \"billing1Q\": \"Replace with your billing-related question\",\r\n \"billing1A\": \"Provide clear information about your billing practices, payment methods, or pricing structure. Be transparent and specific about costs, payment terms, and any relevant policies. Use Promake to add details that help customers understand your billing process.\",\r\n \"billing2Q\": \"This question should be replaced with real billing FAQ\",\r\n \"billing2A\": \"Explain billing-related policies, procedures, or options in detail. Address common concerns about payments, refunds, or invoicing. Use Promake to provide accurate information that builds trust in your billing practices.\",\r\n \"billing3Q\": \"Customize this billing question\",\r\n \"billing3A\": \"Give a thorough answer about billing or payment matters. Include important details about pricing tiers, payment schedules, or billing support. Use Promake to ensure customers have all the billing information they need.\",\r\n \"technicalTitle\": \"Technical\",\r\n \"technical1Q\": \"Replace with your technical question\",\r\n \"technical1A\": \"Provide technical information that helps users understand system requirements, compatibility, or implementation details. Be specific but avoid unnecessary jargon. Use Promake to add technical details that are relevant and helpful to your users.\",\r\n \"technical2Q\": \"This is a placeholder technical question\",\r\n \"technical2A\": \"Explain technical aspects in a way that's accessible to your audience. Include specific details about features, integrations, or technical capabilities. Use Promake to customize with technical information that addresses common questions.\",\r\n \"technical3Q\": \"Customize this final technical question\",\r\n \"technical3A\": \"Give a detailed technical answer that provides the specific information users need. Include relevant specifications, limitations, or technical support options. Use Promake to ensure technical questions are answered thoroughly and accurately.\"\r\n}"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "faq-categorized/lang/tr.json",
|
|
31
31
|
"type": "registry:lang",
|
|
32
32
|
"target": "$modules$/faq-categorized/lang/tr.json",
|
|
33
|
-
"content": "{\r\n \"title\": \"Sıkça Sorulan Sorular\",\r\n \"subtitle\": \"Bu giriş metni ziyaretçilerin kategorize edilmiş SSS'lerinizde gezinmesine yardımcı olur. Kategorilerin nasıl düzenlendiğini açıklayın, ziyaretçileri ilgili bölümleri keşfetmeye teşvik edin veya ek sorular için müsait olduğunuzdan bahsedin. Promake ile SSS yapınıza yardımcı bir rehber oluşturun.\",\r\n \"generalTitle\": \"Genel\",\r\n \"general1Q\": \"Bunu ilk genel sorunuzla değiştirin\",\r\n \"general1A\": \"Bu genel soruya kapsamlı bir cevap verin. Ziyaretçilerin teklifinizi genel düzeyde anlamalarına yardımcı olacak temel bilgileri ekleyin. Promake ile ne yaptığınız ve nasıl çalıştığı hakkındaki yaygın başlangıç sorularını ele alan özel detaylarla özelleştirin.\",\r\n \"general2Q\": \"Bu özelleştirilmesi gereken bir placeholder sorudur\",\r\n \"general2A\": \"Genel sorular olan ziyaretçilere değer sağlayan açık ve bilgilendirici bir cevap verin. Kapsamlı ama anlaşılır olun. Promake ile yeni gelenlerin teklifinizi ve faydalarını anlamalarına yardımcı olan bilgiler ekleyin.\",\r\n \"general3Q\": \"Bu soruyu SSS bölümünüz için özelleştirin\",\r\n \"general3A\": \"Teklifinizin bu genel yönünü ele alan detaylı bir yanıt sunun. İlgili bağlam ve örnekler ekleyin. Promake ile bu cevabın ziyaretçilerin sağladığınız şeyin temellerini anlamalarına yardımcı olduğundan emin olun.\"
|
|
33
|
+
"content": "{\r\n \"title\": \"Sıkça Sorulan Sorular\",\r\n \"subtitle\": \"Bu giriş metni ziyaretçilerin kategorize edilmiş SSS'lerinizde gezinmesine yardımcı olur. Kategorilerin nasıl düzenlendiğini açıklayın, ziyaretçileri ilgili bölümleri keşfetmeye teşvik edin veya ek sorular için müsait olduğunuzdan bahsedin. Promake ile SSS yapınıza yardımcı bir rehber oluşturun.\",\r\n \"generalTitle\": \"Genel\",\r\n \"general1Q\": \"Bunu ilk genel sorunuzla değiştirin\",\r\n \"general1A\": \"Bu genel soruya kapsamlı bir cevap verin. Ziyaretçilerin teklifinizi genel düzeyde anlamalarına yardımcı olacak temel bilgileri ekleyin. Promake ile ne yaptığınız ve nasıl çalıştığı hakkındaki yaygın başlangıç sorularını ele alan özel detaylarla özelleştirin.\",\r\n \"general2Q\": \"Bu özelleştirilmesi gereken bir placeholder sorudur\",\r\n \"general2A\": \"Genel sorular olan ziyaretçilere değer sağlayan açık ve bilgilendirici bir cevap verin. Kapsamlı ama anlaşılır olun. Promake ile yeni gelenlerin teklifinizi ve faydalarını anlamalarına yardımcı olan bilgiler ekleyin.\",\r\n \"general3Q\": \"Bu soruyu SSS bölümünüz için özelleştirin\",\r\n \"general3A\": \"Teklifinizin bu genel yönünü ele alan detaylı bir yanıt sunun. İlgili bağlam ve örnekler ekleyin. Promake ile bu cevabın ziyaretçilerin sağladığınız şeyin temellerini anlamalarına yardımcı olduğundan emin olun.\",\r\n \"billingTitle\": \"Faturalama\",\r\n \"billing1Q\": \"Faturalama ile ilgili sorunuzla değiştirin\",\r\n \"billing1A\": \"Faturalama uygulamalarınız, ödeme yöntemleriniz veya fiyatlandırma yapınız hakkında açık bilgi sağlayın. Maliyetler, ödeme koşulları ve ilgili politikalar konusunda şeffaf ve spesifik olun. Promake ile müşterilerin faturalama sürecinizi anlamalarına yardımcı olan detaylar ekleyin.\",\r\n \"billing2Q\": \"Bu soru gerçek faturalama SSS'si ile değiştirilmelidir\",\r\n \"billing2A\": \"Faturalandırmayla ilgili politikaları, prosedürleri veya seçenekleri detaylı açıklayın. Ödemeler, iadeler veya faturalandırma hakkındaki yaygın endişeleri ele alın. Promake ile faturalama uygulamalarınıza güven oluşturan doğru bilgiler sağlayın.\",\r\n \"billing3Q\": \"Bu faturalama sorusunu özelleştirin\",\r\n \"billing3A\": \"Faturalama veya ödeme konuları hakkında kapsamlı bir cevap verin. Fiyatlandırma katmanları, ödeme programları veya faturalama desteği hakkında önemli detayları ekleyin. Promake ile müşterilerin ihtiyaç duydukları tüm faturalama bilgilerine sahip olduklarından emin olun.\",\r\n \"technicalTitle\": \"Teknik\",\r\n \"technical1Q\": \"Teknik sorunuzla değiştirin\",\r\n \"technical1A\": \"Kullanıcıların sistem gereksinimlerini, uyumluluğu veya uygulama detaylarını anlamalarına yardımcı olan teknik bilgiler sağlayın. Spesifik olun ama gereksiz jargondan kaçının. Promake ile kullanıcılarınız için alakalı ve yardımcı olan teknik detaylar ekleyin.\",\r\n \"technical2Q\": \"Bu bir placeholder teknik sorudur\",\r\n \"technical2A\": \"Teknik yönleri hedef kitlenize erişilebilir bir şekilde açıklayın. Özellikler, entegrasyonlar veya teknik yetenekler hakkında özel detaylar ekleyin. Promake ile yaygın soruları ele alan teknik bilgilerle özelleştirin.\",\r\n \"technical3Q\": \"Bu son teknik soruyu özelleştirin\",\r\n \"technical3A\": \"Kullanıcıların ihtiyaç duyduğu özel bilgiyi sağlayan detaylı bir teknik cevap verin. İlgili özellikleri, sınırlamaları veya teknik destek seçeneklerini ekleyin. Promake ile teknik soruların kapsamlı ve doğru şekilde yanıtlandığından emin olun.\"\r\n}"
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"exports": {
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"path": "faq-simple/lang/en.json",
|
|
25
25
|
"type": "registry:lang",
|
|
26
26
|
"target": "$modules$/faq-simple/lang/en.json",
|
|
27
|
-
"content": "{\r\n \"title\": \"Frequently Asked Questions\",\r\n \"subtitle\": \"
|
|
27
|
+
"content": "{\r\n \"title\": \"Frequently Asked Questions\",\r\n \"subtitle\": \"Find answers to common questions. Contact us if you need more help.\",\r\n \"q1\": \"How do I get started?\",\r\n \"a1\": \"Getting started is easy. Simply sign up for an account and follow our onboarding guide.\",\r\n \"q2\": \"What payment methods do you accept?\",\r\n \"a2\": \"We accept all major credit cards, PayPal, and bank transfers.\",\r\n \"q3\": \"Can I cancel my subscription anytime?\",\r\n \"a3\": \"Yes, you can cancel your subscription at any time from your account settings.\",\r\n \"q4\": \"Do you offer refunds?\",\r\n \"a4\": \"We offer a 30-day money-back guarantee for all new purchases.\",\r\n \"q5\": \"How can I contact support?\",\r\n \"a5\": \"You can reach our support team via email, live chat, or phone during business hours.\",\r\n \"q6\": \"Is my data secure?\",\r\n \"a6\": \"Yes, we use industry-standard encryption and security measures to protect your data.\"\r\n}"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "faq-simple/lang/tr.json",
|
|
31
31
|
"type": "registry:lang",
|
|
32
32
|
"target": "$modules$/faq-simple/lang/tr.json",
|
|
33
|
-
"content": "{\r\n \"title\": \"Sıkça Sorulan Sorular\",\r\n \"subtitle\": \"
|
|
33
|
+
"content": "{\r\n \"title\": \"Sıkça Sorulan Sorular\",\r\n \"subtitle\": \"Sık sorulan sorulara cevaplar. Daha fazla yardım için iletişime geçin.\",\r\n \"q1\": \"Nasıl başlarım?\",\r\n \"a1\": \"Başlamak kolay. Sadece hesap oluşturun ve başlangıç rehberimizi takip edin.\",\r\n \"q2\": \"Hangi ödeme yöntemlerini kabul ediyorsunuz?\",\r\n \"a2\": \"Tüm büyük kredi kartlarını, PayPal ve banka havalelerini kabul ediyoruz.\",\r\n \"q3\": \"Aboneliğimi istediğim zaman iptal edebilir miyim?\",\r\n \"a3\": \"Evet, aboneliğinizi istediğiniz zaman hesap ayarlarından iptal edebilirsiniz.\",\r\n \"q4\": \"Para iadesi yapıyor musunuz?\",\r\n \"a4\": \"Tüm yeni satın alımlar için 30 günlük para iade garantisi sunuyoruz.\",\r\n \"q5\": \"Destekle nasıl iletişime geçebilirim?\",\r\n \"a5\": \"Destek ekibimize e-posta, canlı sohbet veya telefon ile mesai saatlerinde ulaşabilirsiniz.\",\r\n \"q6\": \"Verilerim güvende mi?\",\r\n \"a6\": \"Evet, verilerinizi korumak için endüstri standardı şifreleme ve güvenlik önlemleri kullanıyoruz.\"\r\n}"
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"exports": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "feature-section/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/feature-section/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"heading\": \"Your Site Title Here\",\r\n \"description\": \"
|
|
25
|
+
"content": "{\r\n \"heading\": \"Your Site Title Here\",\r\n \"description\": \"Discover powerful features designed to help you succeed.\",\r\n \"feature1\": \"Key feature or benefit #1\",\r\n \"feature2\": \"Key feature or benefit #2\",\r\n \"feature3\": \"Key feature or benefit #3\",\r\n \"primaryButton\": \"Learn More\",\r\n \"secondaryButton\": \"Get Started\",\r\n \"imageAlt\": \"Site Preview\"\r\n}"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "feature-section/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/feature-section/lang/tr.json",
|
|
31
|
-
"content": "{\r\n \"heading\": \"Site Başlığınız\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"heading\": \"Site Başlığınız\",\r\n \"description\": \"Başarılı olmanıza yardımcı olmak için tasarlanmış güçlü özellikleri keşfedin.\",\r\n \"feature1\": \"Anahtar özellik veya avantaj #1\",\r\n \"feature2\": \"Anahtar özellik veya avantaj #2\",\r\n \"feature3\": \"Anahtar özellik veya avantaj #3\",\r\n \"primaryButton\": \"Daha Fazla Öğren\",\r\n \"secondaryButton\": \"Başlayın\",\r\n \"imageAlt\": \"Site Önizlemesi\"\r\n}"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
"path": "hero-cta/lang/en.json",
|
|
25
25
|
"type": "registry:lang",
|
|
26
26
|
"target": "$modules$/hero-cta/lang/en.json",
|
|
27
|
-
"content": "{\r\n \"heading\": \"
|
|
27
|
+
"content": "{\r\n \"heading\": \"Build Something Amazing Today\",\r\n \"description\": \"Create powerful solutions that help you achieve your goals faster and more efficiently.\",\r\n \"reviews\": \"Trusted by 10,000+ users worldwide\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Learn More\",\r\n \"imageAlt\": \"Hero image\"\r\n}\r\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "hero-cta/lang/tr.json",
|
|
31
31
|
"type": "registry:lang",
|
|
32
32
|
"target": "$modules$/hero-cta/lang/tr.json",
|
|
33
|
-
"content": "{\r\n \"heading\": \"
|
|
33
|
+
"content": "{\r\n \"heading\": \"Bugün Harika Bir Şey İnşa Edin\",\r\n \"description\": \"Hedeflerinize daha hızlı ve verimli ulaşmanıza yardımcı olan güçlü çözümler oluşturun.\",\r\n \"reviews\": \"Dünya çapında 10.000+ kullanıcı tarafından güveniliyor\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Daha Fazla Bilgi\",\r\n \"imageAlt\": \"Hero görseli\"\r\n}\r\n"
|
|
34
34
|
}
|
|
35
35
|
],
|
|
36
36
|
"exports": {
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "hero-gradient/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/hero-gradient/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"badge\": \"
|
|
25
|
+
"content": "{\r\n \"badge\": \"New Feature Available\",\r\n \"headingLine1\": \"Build Something Amazing\",\r\n \"headingLine2\": \"with your site headline\",\r\n \"description\": \"Create powerful solutions that drive results for your business.\",\r\n \"primaryCta\": \"Get Started\",\r\n \"secondaryCta\": \"Contact Sales\",\r\n \"users\": \"Active Users\",\r\n \"uptime\": \"Uptime\",\r\n \"support\": \"Support\"\r\n}\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "hero-gradient/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/hero-gradient/lang/tr.json",
|
|
31
|
-
"content": "{\r\n \"badge\": \"
|
|
31
|
+
"content": "{\r\n \"badge\": \"Yeni Özellik Mevcut\",\r\n \"headingLine1\": \"Harika Bir Şey İnşa Edin\",\r\n \"headingLine2\": \"site başlığınızla güncellemesini isteyin\",\r\n \"description\": \"İşiniz için sonuç odaklı güçlü çözümler oluşturun.\",\r\n \"primaryCta\": \"Başlayın\",\r\n \"secondaryCta\": \"Satış ile İletişim\",\r\n \"users\": \"Aktif Kullanıcı\",\r\n \"uptime\": \"Çalışma Süresi\",\r\n \"support\": \"Destek\"\r\n}\r\n"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
package/dist/registry/hero.json
CHANGED
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
"path": "hero/lang/en.json",
|
|
23
23
|
"type": "registry:lang",
|
|
24
24
|
"target": "$modules$/hero/lang/en.json",
|
|
25
|
-
"content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"
|
|
25
|
+
"content": "{\r\n \"discover\": \"Discover\",\r\n \"amazing\": \"Inspiring\",\r\n \"content\": \"Stories\",\r\n \"subtitle\": \"Explore inspiring stories and meaningful content.\",\r\n \"readLatest\": \"Start Reading\",\r\n \"exploreTopics\": \"Explore Topics\"\r\n}\r\n"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"path": "hero/lang/tr.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/hero/lang/tr.json",
|
|
31
|
-
"content": "{\r\n \"discover\": \"Keşfedin\",\r\n \"amazing\": \"İlham Verici\",\r\n \"content\": \"Hikayeler\",\r\n \"subtitle\": \"
|
|
31
|
+
"content": "{\r\n \"discover\": \"Keşfedin\",\r\n \"amazing\": \"İlham Verici\",\r\n \"content\": \"Hikayeler\",\r\n \"subtitle\": \"İlham verici hikayeleri ve anlamlı içerikleri keşfedin.\",\r\n \"readLatest\": \"Okumaya Başlayın\",\r\n \"exploreTopics\": \"Konuları Keşfedin\"\r\n}\r\n"
|
|
32
32
|
}
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"path": "landing-page-app/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/landing-page-app/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Mobile App\",\r\n \"badge\": \"Available on iOS & Android\",\r\n \"heroTitle\": \"Your App Name Here\",\r\n \"heroDescription\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Mobile App\",\r\n \"badge\": \"Available on iOS & Android\",\r\n \"heroTitle\": \"Your App Name Here\",\r\n \"heroDescription\": \"The all-in-one app that simplifies your daily tasks and boosts productivity.\",\r\n \"downloadOn\": \"Download on the\",\r\n \"getItOn\": \"Get it on\",\r\n \"downloads\": \"Downloads\",\r\n \"inCategory\": \"In Category\",\r\n \"taskComplete\": \"Task Complete!\",\r\n \"newMessage\": \"New Message\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Why You'll Love It\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Lightning-fast performance keeps your app running smoothly at all times.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Stay informed with intelligent notifications tailored to your preferences.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Your data is protected with bank-level encryption and privacy controls.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Access all your content offline, anytime and anywhere you need it.\",\r\n \"screenshotsLabel\": \"Screenshots\",\r\n \"screenshotsTitle\": \"See It in Action\",\r\n \"reviewsLabel\": \"Reviews\",\r\n \"reviewsTitle\": \"What Users Say\",\r\n \"review1\": \"This app has completely changed how I manage my daily tasks. Clean interface and super intuitive!\",\r\n \"review2\": \"Love the offline mode! I can work anywhere without worrying about connectivity. 5 stars!\",\r\n \"review3\": \"Best app in its category. The team clearly cares about user experience and it shows.\",\r\n \"ctaTitle\": \"Download Now\",\r\n \"ctaDescription\": \"Join millions of users worldwide. Download now and start simplifying your life today!\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "landing-page-app/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/landing-page-app/lang/tr.json",
|
|
37
|
-
"content": "{\r\n \"title\": \"Mobil Uygulama\",\r\n \"badge\": \"iOS ve Android'de Mevcut\",\r\n \"heroTitle\": \"Hayatınız, Tek Uygulamada Basitleştirildi\",\r\n \"heroDescription\": \"
|
|
37
|
+
"content": "{\r\n \"title\": \"Mobil Uygulama\",\r\n \"badge\": \"iOS ve Android'de Mevcut\",\r\n \"heroTitle\": \"Hayatınız, Tek Uygulamada Basitleştirildi\",\r\n \"heroDescription\": \"Günlük görevlerinizi basitleştiren ve verimliliğinizi artıran hepsi bir arada uygulama.\",\r\n \"downloadOn\": \"Şuradan indir:\",\r\n \"getItOn\": \"Şuradan alın:\",\r\n \"downloads\": \"İndirme\",\r\n \"inCategory\": \"Kategorisinde\",\r\n \"taskComplete\": \"Görev Tamamlandı!\",\r\n \"newMessage\": \"Yeni Mesaj\",\r\n \"featuresLabel\": \"Özellikler\",\r\n \"featuresTitle\": \"Neden Seveceksiniz\",\r\n \"feature1Title\": \"Işık Hızında\",\r\n \"feature1Desc\": \"Yıldırım hızında performans, uygulamanızı her zaman sorunsuz çalıştırır.\",\r\n \"feature2Title\": \"Akıllı Bildirimler\",\r\n \"feature2Desc\": \"Tercihlerinize göre özelleştirilmiş akıllı bildirimlerle bilgilenin.\",\r\n \"feature3Title\": \"Güvenli & Özel\",\r\n \"feature3Desc\": \"Verileriniz banka seviyesinde şifreleme ve gizlilik kontrolleriyle korunur.\",\r\n \"feature4Title\": \"Çevrimdışı Mod\",\r\n \"feature4Desc\": \"Tüm içeriğinize çevrimdışı, istediğiniz zaman ve yerde erişin.\",\r\n \"screenshotsLabel\": \"Ekran Görüntüleri\",\r\n \"screenshotsTitle\": \"Uygulamada Görün\",\r\n \"reviewsLabel\": \"Yorumlar\",\r\n \"reviewsTitle\": \"Kullanıcılar Ne Diyor\",\r\n \"review1\": \"Bu uygulama günlük görevlerimi yönetme şeklimi tamamen değiştirdi. Temiz arayüz ve süper sezgisel!\",\r\n \"review2\": \"Çevrimdışı modu çok seviyorum! Bağlantı endişesi olmadan her yerde çalışabiliyorum. 5 yıldız!\",\r\n \"review3\": \"Kategorisindeki en iyi uygulama. Ekibin kullanıcı deneyimini önemsediği belli oluyor.\",\r\n \"ctaTitle\": \"Şimdi İndirin\",\r\n \"ctaDescription\": \"iOS ve Android'de ücretsiz olarak mevcut. Yolculuğunuza bugün başlayın.\"\r\n}\r\n"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -28,13 +28,13 @@
|
|
|
28
28
|
"path": "landing-page-saas/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/landing-page-saas/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"New Feature Available\",\r\n \"heroTitle\": \"Your Product Name Here\",\r\n \"heroDescription\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"New Feature Available\",\r\n \"heroTitle\": \"Your Product Name Here\",\r\n \"heroDescription\": \"Streamline your workflow and boost team productivity with powerful automation tools.\",\r\n \"startFree\": \"Start Free Trial\",\r\n \"watchDemo\": \"Watch Demo\",\r\n \"noCreditCard\": \"No credit card required\",\r\n \"stat1Label\": \"Active Users\",\r\n \"stat2Label\": \"Uptime\",\r\n \"stat3Label\": \"Tasks Done\",\r\n \"stat4Label\": \"Rating\",\r\n \"featuresLabel\": \"Features\",\r\n \"featuresTitle\": \"Everything You Need\",\r\n \"featuresDescription\": \"Discover the tools and features that make your work easier and more productive.\",\r\n \"feature1Title\": \"Feature One\",\r\n \"feature1Desc\": \"Lightning-fast performance that saves you time and boosts efficiency.\",\r\n \"feature2Title\": \"Feature Two\",\r\n \"feature2Desc\": \"Bank-level encryption keeps your data secure and protected at all times.\",\r\n \"feature3Title\": \"Feature Three\",\r\n \"feature3Desc\": \"Deep insights and analytics help you make better data-driven decisions.\",\r\n \"feature4Title\": \"Feature Four\",\r\n \"feature4Desc\": \"Real-time collaboration tools keep your team connected and productive.\",\r\n \"feature5Title\": \"Feature Five\",\r\n \"feature5Desc\": \"99.9% uptime guarantee ensures your work is always accessible.\",\r\n \"feature6Title\": \"Feature Six\",\r\n \"feature6Desc\": \"Smart automation handles repetitive tasks so you can focus on what matters.\",\r\n \"testimonialsLabel\": \"Testimonials\",\r\n \"testimonialsTitle\": \"What Our Customers Say\",\r\n \"testimonial1\": \"This tool transformed how our team works. We've cut project delivery time by 40% and improved collaboration significantly.\",\r\n \"testimonial2\": \"The automation features alone save us 10+ hours per week. It's become essential to our workflow.\",\r\n \"testimonial3\": \"Incredibly intuitive and powerful. The customer support is outstanding, and the ROI has been remarkable.\",\r\n \"pricingLabel\": \"Pricing\",\r\n \"pricingTitle\": \"Simple, Transparent Pricing\",\r\n \"starterPlan\": \"Starter\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Enterprise\",\r\n \"custom\": \"Custom\",\r\n \"mostPopular\": \"Most Popular\",\r\n \"getStarted\": \"Get Started\",\r\n \"viewAllPlans\": \"View all pricing plans\",\r\n \"ctaTitle\": \"Ready to Get Started?\",\r\n \"ctaDescription\": \"Join thousands of teams already using our platform. Start your free 14-day trial today—no credit card required.\",\r\n \"ctaButton\": \"Get Started\"\r\n}\r\n"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "landing-page-saas/lang/tr.json",
|
|
35
35
|
"type": "registry:lang",
|
|
36
36
|
"target": "$modules$/landing-page-saas/lang/tr.json",
|
|
37
|
-
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"Artık AI destekli özelliklerle\",\r\n \"heroTitle\": \"Daha İyi Ürünler, Her Zamankinden Daha Hızlı Oluşturun\",\r\n \"heroDescription\": \"
|
|
37
|
+
"content": "{\r\n \"title\": \"SaaS Landing\",\r\n \"badge\": \"Artık AI destekli özelliklerle\",\r\n \"heroTitle\": \"Daha İyi Ürünler, Her Zamankinden Daha Hızlı Oluşturun\",\r\n \"heroDescription\": \"Güçlü otomasyon araçlarıyla iş akışınızı optimize edin ve ekip verimliliğini artırın.\",\r\n \"startFree\": \"Ücretsiz Deneyin\",\r\n \"watchDemo\": \"Demo İzle\",\r\n \"noCreditCard\": \"Kredi kartı gerekmez • 14 gün ücretsiz deneme\",\r\n \"stat1Label\": \"Aktif Kullanıcı\",\r\n \"stat2Label\": \"Çalışma Süresi\",\r\n \"stat3Label\": \"Tamamlanan Görev\",\r\n \"stat4Label\": \"Kullanıcı Puanı\",\r\n \"featuresLabel\": \"Özellikler\",\r\n \"featuresTitle\": \"Başarı İçin İhtiyacınız Olan Her Şey\",\r\n \"featuresDescription\": \"İşinizi daha kolay ve verimli hale getiren araçları ve özellikleri keşfedin.\",\r\n \"feature1Title\": \"Işık Hızında\",\r\n \"feature1Desc\": \"Yıldırım hızında performans, zamandan tasarruf sağlar ve verimliliği artırır.\",\r\n \"feature2Title\": \"Varsayılan Olarak Güvenli\",\r\n \"feature2Desc\": \"Banka seviyesinde şifreleme, verilerinizi her zaman güvende tutar.\",\r\n \"feature3Title\": \"Güçlü Analitik\",\r\n \"feature3Desc\": \"Derin görüşler ve analizler, daha iyi veri odaklı kararlar almanıza yardımcı olur.\",\r\n \"feature4Title\": \"Takım İşbirliği\",\r\n \"feature4Desc\": \"Gerçek zamanlı işbirliği araçları ekibinizi bağlı ve verimli tutar.\",\r\n \"feature5Title\": \"7/24 Kullanılabilirlik\",\r\n \"feature5Desc\": \"%99.9 çalışma süresi garantisi, işinizin her zaman erişilebilir olmasını sağlar.\",\r\n \"feature6Title\": \"AI Destekli\",\r\n \"feature6Desc\": \"Akıllı otomasyon tekrarlayan görevleri halleder, siz önemli işlere odaklanın.\",\r\n \"testimonialsLabel\": \"Referanslar\",\r\n \"testimonialsTitle\": \"Dünya Genelinde Ekipler Tarafından Seviliyor\",\r\n \"testimonial1\": \"Bu araç ekibimizin çalışma şeklini değiştirdi. Proje teslim süremizi %40 azalttık ve işbirliğimizi önemli ölçüde geliştirdik.\",\r\n \"testimonial2\": \"Sadece otomasyon özellikleri bile bize haftada 10+ saat kazandırıyor. İş akışımızın vazgeçilmez bir parçası oldu.\",\r\n \"testimonial3\": \"İnanılmaz sezgisel ve güçlü. Müşteri desteği olağanüstü ve yatırım getirisi dikkat çekici.\",\r\n \"pricingLabel\": \"Fiyatlandırma\",\r\n \"pricingTitle\": \"Basit, Şeffaf Fiyatlandırma\",\r\n \"starterPlan\": \"Başlangıç\",\r\n \"proPlan\": \"Pro\",\r\n \"enterprisePlan\": \"Kurumsal\",\r\n \"custom\": \"Özel\",\r\n \"mostPopular\": \"En Popüler\",\r\n \"getStarted\": \"Başlayın\",\r\n \"viewAllPlans\": \"Tüm planları görüntüle\",\r\n \"ctaTitle\": \"İş Akışınızı Dönüştürmeye Hazır mısınız?\",\r\n \"ctaDescription\": \"Platformumuzu kullanan binlerce ekibe katılın. 14 günlük ücretsiz denemenizi bugün başlatın—kredi kartı gerekmez.\",\r\n \"ctaButton\": \"Ücretsiz Denemenizi Başlatın\"\r\n}\r\n"
|
|
38
38
|
}
|
|
39
39
|
],
|
|
40
40
|
"exports": {
|
|
@@ -26,13 +26,13 @@
|
|
|
26
26
|
"path": "portfolio-page/lang/en.json",
|
|
27
27
|
"type": "registry:lang",
|
|
28
28
|
"target": "$modules$/portfolio-page/lang/en.json",
|
|
29
|
-
"content": "{\r\n \"pageTitle\": \"Portfolio\",\r\n \"title\": \"Our Portfolio\",\r\n \"subtitle\": \"
|
|
29
|
+
"content": "{\r\n \"pageTitle\": \"Portfolio\",\r\n \"title\": \"Our Portfolio\",\r\n \"subtitle\": \"Showcase of our best work and projects.\",\r\n \"noItems\": \"No projects found in this category.\"\r\n}"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"path": "portfolio-page/lang/tr.json",
|
|
33
33
|
"type": "registry:lang",
|
|
34
34
|
"target": "$modules$/portfolio-page/lang/tr.json",
|
|
35
|
-
"content": "{\r\n \"pageTitle\": \"Portfolyo\",\r\n \"title\": \"Portfolyomuz\",\r\n \"subtitle\": \"
|
|
35
|
+
"content": "{\r\n \"pageTitle\": \"Portfolyo\",\r\n \"title\": \"Portfolyomuz\",\r\n \"subtitle\": \"En iyi çalışmalarımız ve projelerimiz.\",\r\n \"noItems\": \"Bu kategoride proje bulunamadı.\"\r\n}"
|
|
36
36
|
}
|
|
37
37
|
],
|
|
38
38
|
"exports": {
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "pricing-section/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/pricing-section/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"
|
|
28
|
+
"content": "{\r\n \"label\": \"Pricing\",\r\n \"title\": \"Simple, Transparent Pricing\",\r\n \"subtitle\": \"Choose the perfect plan for your needs. All plans include a 14-day free trial.\",\r\n \"perMonth\": \"/month\",\r\n \"popular\": \"Most Popular\",\r\n \"cta\": \"Get Started\",\r\n \"guarantee\": \"30-day money-back guarantee. Cancel anytime, no questions asked.\",\r\n \"starterName\": \"Starter\",\r\n \"starterDesc\": \"Perfect for individuals and small projects\",\r\n \"starterPrice\": \"$9\",\r\n \"starterFeature1\": \"Up to 5 projects\",\r\n \"starterFeature2\": \"10GB storage\",\r\n \"starterFeature3\": \"Basic analytics\",\r\n \"starterFeature4\": \"Email support\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Best for growing teams and businesses\",\r\n \"proPrice\": \"$29\",\r\n \"proFeature1\": \"Unlimited projects\",\r\n \"proFeature2\": \"100GB storage\",\r\n \"proFeature3\": \"Advanced analytics\",\r\n \"proFeature4\": \"Priority support\",\r\n \"proFeature5\": \"Team collaboration tools\",\r\n \"enterpriseName\": \"Advanced\",\r\n \"enterpriseDesc\": \"For large organizations with custom needs\",\r\n \"enterprisePrice\": \"$99\",\r\n \"enterpriseFeature1\": \"Everything in Pro\",\r\n \"enterpriseFeature2\": \"Unlimited storage\",\r\n \"enterpriseFeature3\": \"Custom integrations\",\r\n \"enterpriseFeature4\": \"Dedicated account manager\",\r\n \"enterpriseFeature5\": \"Advanced security & compliance\",\r\n \"enterpriseFeature6\": \"SLA & 24/7 support\"\r\n}\r\n"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "pricing-section/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/pricing-section/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"
|
|
34
|
+
"content": "{\r\n \"label\": \"Fiyatlandırma\",\r\n \"title\": \"Basit, Şeffaf Fiyatlandırma\",\r\n \"subtitle\": \"İhtiyaçlarınıza uygun planı seçin. Tüm planlarda 14 günlük ücretsiz deneme dahildir.\",\r\n \"perMonth\": \"/ay\",\r\n \"popular\": \"En Popüler\",\r\n \"cta\": \"Başlayın\",\r\n \"guarantee\": \"30 gün para iade garantisi. İstediğiniz zaman iptal edin, sorulsuz sualsiz.\",\r\n \"starterName\": \"Başlangıç\",\r\n \"starterDesc\": \"Bireyler ve küçük projeler için mükemmel\",\r\n \"starterPrice\": \"99₺\",\r\n \"starterFeature1\": \"5 projeye kadar\",\r\n \"starterFeature2\": \"10GB depolama\",\r\n \"starterFeature3\": \"Temel analitik\",\r\n \"starterFeature4\": \"E-posta desteği\",\r\n \"proName\": \"Pro\",\r\n \"proDesc\": \"Büyüyen ekipler ve işletmeler için en iyisi\",\r\n \"proPrice\": \"299₺\",\r\n \"proFeature1\": \"Sınırsız proje\",\r\n \"proFeature2\": \"100GB depolama\",\r\n \"proFeature3\": \"Gelişmiş analitik\",\r\n \"proFeature4\": \"Öncelikli destek\",\r\n \"proFeature5\": \"Ekip işbirliği araçları\",\r\n \"enterpriseName\": \"Gelişmiş\",\r\n \"enterpriseDesc\": \"Özel ihtiyaçları olan büyük kuruluşlar için\",\r\n \"enterprisePrice\": \"999₺\",\r\n \"enterpriseFeature1\": \"Pro'daki her şey\",\r\n \"enterpriseFeature2\": \"Sınırsız depolama\",\r\n \"enterpriseFeature3\": \"Özel entegrasyonlar\",\r\n \"enterpriseFeature4\": \"Özel hesap yöneticisi\",\r\n \"enterpriseFeature5\": \"Gelişmiş güvenlik ve uyumluluk\",\r\n \"enterpriseFeature6\": \"SLA ve 7/24 destek\"\r\n}\r\n"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"path": "products-page/products-page.tsx",
|
|
25
25
|
"type": "registry:page",
|
|
26
26
|
"target": "$modules$/products-page/products-page.tsx",
|
|
27
|
-
"content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: (key: string, options?: any) => string;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"grid\"\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"list\"\n />\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
|
|
27
|
+
"content": "import { useState, useRef, useCallback, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router\";\nimport { useTranslation } from \"react-i18next\";\nimport { usePageTitle } from \"@/hooks/use-page-title\";\nimport { Filter, Grid, List } from \"lucide-react\";\nimport { Layout } from \"@/components/Layout\";\nimport { Button } from \"@/components/ui/button\";\nimport { FadeIn } from \"@/modules/animations\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from \"@/components/ui/sheet\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { ProductCard } from \"@/modules/product-card/product-card\";\nimport { useProducts, useCategories } from \"@/modules/ecommerce-core\";\nimport type { Product, Category } from \"@/modules/ecommerce-core/types\";\n\ninterface FilterSidebarProps {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n t: any;\n categories: Category[];\n selectedCategories: string[];\n handleCategoryChange: (category: string, checked: boolean) => void;\n selectedFeatures: string[];\n handleFeatureChange: (feature: string, checked: boolean) => void;\n minPriceRef: React.RefObject<HTMLInputElement | null>;\n maxPriceRef: React.RefObject<HTMLInputElement | null>;\n searchParams: URLSearchParams;\n handlePriceFilter: () => void;\n}\n\nfunction FilterSidebar({\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n}: FilterSidebarProps) {\n return (\n <div className=\"space-y-6\">\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"categories\", \"Categories\")}\n </h3>\n <div className=\"space-y-3\">\n {categories.map((category) => (\n <div\n key={category.id}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={`category-${category.id}`}\n checked={selectedCategories.includes(category.slug)}\n onCheckedChange={(checked) =>\n handleCategoryChange(category.slug, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={`category-${category.id}`}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {category.name}\n </label>\n </div>\n ))}\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"priceRange\", \"Price Range\")}\n </h3>\n <div className=\"space-y-3 p-3 bg-muted/30 rounded-lg\">\n <div className=\"grid grid-cols-2 gap-3\">\n <input\n ref={minPriceRef}\n type=\"number\"\n placeholder={t(\"minPrice\", \"Min\")}\n defaultValue={searchParams.get(\"minPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n <input\n ref={maxPriceRef}\n type=\"number\"\n placeholder={t(\"maxPrice\", \"Max\")}\n defaultValue={searchParams.get(\"maxPrice\") || \"\"}\n onKeyDown={(e) => e.key === \"Enter\" && handlePriceFilter()}\n className=\"w-full px-3 py-2 border border-input rounded-lg text-sm bg-background\"\n />\n </div>\n </div>\n </div>\n\n <div>\n <h3 className=\"font-semibold mb-4 text-base\">\n {t(\"features\", \"Features\")}\n </h3>\n <div className=\"space-y-3\">\n {[\n { key: \"on_sale\", label: t(\"onSale\", \"On Sale\") },\n { key: \"is_new\", label: t(\"newArrivals\", \"New Arrivals\") },\n { key: \"featured\", label: t(\"featuredLabel\", \"Featured\") },\n { key: \"in_stock\", label: t(\"inStock\", \"In Stock\") },\n ].map((feature) => (\n <div\n key={feature.key}\n className=\"flex items-center space-x-3 p-2 rounded-lg hover:bg-muted/50 transition-colors\"\n >\n <Checkbox\n id={feature.key}\n checked={selectedFeatures.includes(feature.key)}\n onCheckedChange={(checked) =>\n handleFeatureChange(feature.key, checked as boolean)\n }\n className=\"data-[state=checked]:bg-primary data-[state=checked]:border-primary\"\n />\n <label\n htmlFor={feature.key}\n className=\"text-sm font-medium leading-none cursor-pointer flex-1\"\n >\n {feature.label}\n </label>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n\nexport function ProductsPage() {\n const { t } = useTranslation(\"products-page\");\n usePageTitle({ title: t(\"pageTitle\", \"Products\") });\n const { products, loading: productsLoading } = useProducts();\n const { categories, loading: categoriesLoading } = useCategories();\n const loading = productsLoading || categoriesLoading;\n\n const [searchParams, setSearchParams] = useSearchParams();\n const [viewMode, setViewMode] = useState<\"grid\" | \"list\">(\"grid\");\n const [sortBy, setSortBy] = useState(\"featured\");\n const [selectedCategories, setSelectedCategories] = useState<string[]>(() => {\n const categorySlug = searchParams.get(\"category\");\n return categorySlug ? [categorySlug] : [];\n });\n const [selectedFeatures, setSelectedFeatures] = useState<string[]>([]);\n const searchQuery = searchParams.get(\"search\") || \"\";\n const minPriceRef = useRef<HTMLInputElement>(null);\n const maxPriceRef = useRef<HTMLInputElement>(null);\n\n const filteredProducts = useMemo(() => {\n const minPrice = parseFloat(searchParams.get(\"minPrice\") || \"0\") || 0;\n const maxPrice =\n parseFloat(searchParams.get(\"maxPrice\") || \"999999\") || 999999;\n\n let filtered = products.filter((product) => {\n const currentPrice =\n product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n return currentPrice >= minPrice && currentPrice <= maxPrice;\n });\n\n if (selectedCategories.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedCategories.some((selectedCategory) => {\n if (product.category === selectedCategory) return true;\n return product.categories?.some(\n (cat) => cat.slug === selectedCategory\n );\n });\n });\n }\n\n if (selectedFeatures.length > 0) {\n filtered = filtered.filter((product) => {\n return selectedFeatures.every((feature) => {\n switch (feature) {\n case \"on_sale\":\n return product.on_sale;\n case \"is_new\":\n return product.is_new;\n case \"featured\":\n return product.featured;\n case \"in_stock\":\n return product.stock > 0;\n default:\n return true;\n }\n });\n });\n }\n\n // Apply sorting\n return [...filtered].sort((a, b) => {\n switch (sortBy) {\n case \"price-low\":\n return (\n (a.on_sale ? a.sale_price || a.price : a.price) -\n (b.on_sale ? b.sale_price || b.price : b.price)\n );\n case \"price-high\":\n return (\n (b.on_sale ? b.sale_price || b.price : b.price) -\n (a.on_sale ? a.sale_price || a.price : a.price)\n );\n case \"newest\":\n return (\n new Date(b.created_at || 0).getTime() -\n new Date(a.created_at || 0).getTime()\n );\n case \"featured\":\n default:\n return (b.featured ? 1 : 0) - (a.featured ? 1 : 0);\n }\n });\n }, [products, searchParams, selectedFeatures, selectedCategories, sortBy]);\n\n const handlePriceFilter = useCallback(() => {\n const minPrice = minPriceRef.current?.value || \"\";\n const maxPrice = maxPriceRef.current?.value || \"\";\n const params = new URLSearchParams(searchParams);\n if (minPrice) params.set(\"minPrice\", minPrice);\n else params.delete(\"minPrice\");\n if (maxPrice) params.set(\"maxPrice\", maxPrice);\n else params.delete(\"maxPrice\");\n setSearchParams(params);\n }, [searchParams, setSearchParams]);\n\n const handleCategoryChange = useCallback(\n (category: string, checked: boolean) => {\n if (checked) {\n setSelectedCategories((prev) => [...prev, category]);\n } else {\n setSelectedCategories((prev) => prev.filter((c) => c !== category));\n }\n },\n []\n );\n\n const handleFeatureChange = useCallback(\n (feature: string, checked: boolean) => {\n if (checked) {\n setSelectedFeatures((prev) => [...prev, feature]);\n } else {\n setSelectedFeatures((prev) => prev.filter((f) => f !== feature));\n }\n },\n []\n );\n\n const sortOptions = [\n { value: \"featured\", label: t(\"featured\", \"Featured\") },\n { value: \"price-low\", label: t(\"sortPriceLow\", \"Price: Low to High\") },\n { value: \"price-high\", label: t(\"sortPriceHigh\", \"Price: High to Low\") },\n { value: \"newest\", label: t(\"sortNewest\", \"Newest\") },\n ];\n\n const filterSidebarProps: FilterSidebarProps = {\n t,\n categories,\n selectedCategories,\n handleCategoryChange,\n selectedFeatures,\n handleFeatureChange,\n minPriceRef,\n maxPriceRef,\n searchParams,\n handlePriceFilter,\n };\n\n return (\n <Layout>\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4 py-8\">\n <FadeIn className=\"mb-8\">\n <div className=\"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 mb-6\">\n <div className=\"space-y-1\">\n <h1 className=\"text-2xl lg:text-3xl font-bold\">\n {searchQuery\n ? t(\"searchResultsFor\", `Search Results for \"${searchQuery}\"`)\n : t(\"allProducts\", \"All Products\")}\n </h1>\n <p className=\"text-sm lg:text-base text-muted-foreground\">\n {t(\"showing\", \"Showing\")} {filteredProducts.length}{\" \"}\n {t(\"of\", \"of\")} {products.length} {t(\"products\", \"products\")}\n </p>\n </div>\n {searchQuery && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setSearchParams({})}\n className=\"w-fit\"\n >\n {t(\"clearSearch\", \"Clear Search\")}\n </Button>\n )}\n </div>\n\n <div className=\"flex flex-col sm:flex-row gap-3 items-stretch sm:items-center justify-between\">\n <Sheet>\n <SheetTrigger asChild>\n <Button\n variant=\"outline\"\n className=\"lg:hidden w-full sm:w-auto\"\n >\n <Filter className=\"h-4 w-4 mr-2\" />\n {t(\"filters\", \"Filters\")}\n </Button>\n </SheetTrigger>\n <SheetContent side=\"left\" className=\"w-[300px]\">\n <SheetHeader>\n <SheetTitle>{t(\"filters\", \"Filters\")}</SheetTitle>\n <SheetDescription>\n {t(\"refineSearch\", \"Refine your product search\")}\n </SheetDescription>\n </SheetHeader>\n <div className=\"mt-6\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </SheetContent>\n </Sheet>\n\n <div className=\"flex flex-col sm:flex-row items-stretch sm:items-center gap-3\">\n <Select value={sortBy} onValueChange={setSortBy}>\n <SelectTrigger className=\"w-full sm:w-[160px]\">\n <SelectValue placeholder={t(\"sortBy\", \"Sort by\")} />\n </SelectTrigger>\n <SelectContent>\n {sortOptions.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <div className=\"flex border rounded-lg p-1 w-full sm:w-auto\">\n <Button\n variant={viewMode === \"grid\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"grid\")}\n className=\"flex-1 sm:flex-none\"\n >\n <Grid className=\"h-4 w-4\" />\n </Button>\n <Button\n variant={viewMode === \"list\" ? \"default\" : \"ghost\"}\n size=\"sm\"\n onClick={() => setViewMode(\"list\")}\n className=\"flex-1 sm:flex-none\"\n >\n <List className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </div>\n </FadeIn>\n\n <div className=\"flex gap-8\">\n <aside className=\"hidden lg:block w-64 flex-shrink-0\">\n <div className=\"sticky top-24\">\n <FilterSidebar {...filterSidebarProps} />\n </div>\n </aside>\n\n <div className=\"flex-1\">\n {loading ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {[...Array(6)].map((_, i) => (\n <div\n key={i}\n className=\"animate-pulse bg-card rounded-lg shadow-md overflow-hidden\"\n >\n <div className=\"aspect-square bg-muted mb-4\"></div>\n <div className=\"p-4\">\n <div className=\"h-4 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-3 bg-muted rounded w-1/2 mb-3\"></div>\n <div className=\"h-4 bg-muted rounded w-1/3\"></div>\n </div>\n </div>\n ))}\n </div>\n ) : viewMode === \"grid\" ? (\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-8\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"grid\"\n />\n ))}\n </div>\n ) : (\n <div className=\"space-y-6\">\n {filteredProducts.map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n variant=\"list\"\n />\n ))}\n </div>\n )}\n\n {!loading && filteredProducts.length === 0 && (\n <div className=\"text-center py-12\">\n <p className=\"text-muted-foreground\">\n {t(\n \"noProductsFound\",\n \"No products found matching your criteria.\"\n )}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n </Layout>\n );\n}\n\nexport default ProductsPage;\n"
|
|
28
28
|
},
|
|
29
29
|
{
|
|
30
30
|
"path": "products-page/lang/en.json",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"path": "team-page/lang/en.json",
|
|
29
29
|
"type": "registry:lang",
|
|
30
30
|
"target": "$modules$/team-page/lang/en.json",
|
|
31
|
-
"content": "{\r\n \"title\": \"Our Team\",\r\n \"label\": \"Our Team\",\r\n \"heading\": \"Meet the People Behind the Magic\",\r\n \"description\": \"
|
|
31
|
+
"content": "{\r\n \"title\": \"Our Team\",\r\n \"label\": \"Our Team\",\r\n \"heading\": \"Meet the People Behind the Magic\",\r\n \"description\": \"Passionate experts dedicated to delivering exceptional results.\",\r\n \"member1Name\": \"Team Member\",\r\n \"member1Role\": \"CEO & Founder\",\r\n \"member1Bio\": \"Visionary leader with 15+ years in tech innovation.\",\r\n \"member2Name\": \"Team Member\",\r\n \"member2Role\": \"CTO\",\r\n \"member2Bio\": \"Full-stack architect passionate about scalable solutions.\",\r\n \"member3Name\": \"Team Member\",\r\n \"member3Role\": \"Head of Design\",\r\n \"member3Bio\": \"Award-winning designer creating beautiful user experiences.\",\r\n \"member4Name\": \"Team Member\",\r\n \"member4Role\": \"Lead Developer\",\r\n \"member4Bio\": \"Open source contributor and clean code advocate.\",\r\n \"member5Name\": \"Team Member\",\r\n \"member5Role\": \"Product Manager\",\r\n \"member5Bio\": \"Strategic thinker turning ideas into successful products.\",\r\n \"member6Name\": \"Team Member\",\r\n \"member6Role\": \"Marketing Director\",\r\n \"member6Bio\": \"Growth expert with data-driven approach.\",\r\n \"ctaTitle\": \"Want to Join Our Team?\",\r\n \"ctaDescription\": \"We're always looking for talented individuals to join us.\",\r\n \"ctaButton\": \"View Open Positions\"\r\n}"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"path": "team-page/lang/tr.json",
|
|
@@ -25,13 +25,13 @@
|
|
|
25
25
|
"path": "testimonials-grid/lang/en.json",
|
|
26
26
|
"type": "registry:lang",
|
|
27
27
|
"target": "$modules$/testimonials-grid/lang/en.json",
|
|
28
|
-
"content": "{\r\n \"title\": \"What People Say\",\r\n \"subtitle\": \"
|
|
28
|
+
"content": "{\r\n \"title\": \"What People Say\",\r\n \"subtitle\": \"Real feedback from our satisfied customers.\",\r\n \"testimonial1Name\": \"Customer Name\",\r\n \"testimonial1Role\": \"Position, Company\",\r\n \"testimonial1Review\": \"Add customer testimonial here. Include specific details about results achieved and positive experiences.\",\r\n \"testimonial2Name\": \"Client Name\",\r\n \"testimonial2Role\": \"Position, Company\",\r\n \"testimonial2Review\": \"Customer testimonial showcasing a different benefit or use case.\",\r\n \"testimonial3Name\": \"User Name\",\r\n \"testimonial3Role\": \"Position, Company\",\r\n \"testimonial3Review\": \"Testimonial highlighting overall satisfaction or unexpected benefits.\"\r\n}"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "testimonials-grid/lang/tr.json",
|
|
32
32
|
"type": "registry:lang",
|
|
33
33
|
"target": "$modules$/testimonials-grid/lang/tr.json",
|
|
34
|
-
"content": "{\r\n \"title\": \"İnsanların Yorumları\",\r\n \"subtitle\": \"
|
|
34
|
+
"content": "{\r\n \"title\": \"İnsanların Yorumları\",\r\n \"subtitle\": \"Memnun müşterilerimizden gerçek geri bildirimler.\",\r\n \"testimonial1Name\": \"Kullanıcı Adı\",\r\n \"testimonial1Role\": \"Pozisyon, Şirket\",\r\n \"testimonial1Review\": \"Buraya müşteri yorumu eklenecek. Elde edilen sonuçlar ve olumlu deneyimler hakkında özel detaylar içermeli.\",\r\n \"testimonial2Name\": \"Müşteri Adı\",\r\n \"testimonial2Role\": \"Pozisyon, Şirket\",\r\n \"testimonial2Review\": \"Farklı bir faydayı veya kullanım durumunu sergileyen müşteri referansı.\",\r\n \"testimonial3Name\": \"Uzman Adı\",\r\n \"testimonial3Role\": \"Pozisyon, Şirket\",\r\n \"testimonial3Review\": \"Genel memnuniyeti veya şaşırtıcı bir faydayı vurgulayan referans.\"\r\n}"
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
37
|
"exports": {
|