@promakeai/cli 0.3.8 → 0.4.0
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 +3 -3
- package/dist/registry/auth-core.json +1 -1
- package/dist/registry/blog-list-page.json +1 -1
- package/dist/registry/blog-section.json +1 -1
- package/dist/registry/cards-carousel-section.json +2 -2
- package/dist/registry/cart-drawer.json +1 -1
- package/dist/registry/category-section.json +1 -1
- package/dist/registry/checkout-page.json +1 -1
- package/dist/registry/docs/reset-password-page.md +15 -9
- package/dist/registry/favorites-blog-block.json +1 -1
- package/dist/registry/favorites-blog-page.json +1 -1
- package/dist/registry/favorites-ecommerce-block.json +1 -1
- package/dist/registry/favorites-ecommerce-page.json +1 -1
- package/dist/registry/featured-products.json +1 -1
- package/dist/registry/header-ecommerce.json +1 -1
- package/dist/registry/my-orders-page.json +1 -1
- package/dist/registry/order-confirmation-page.json +1 -1
- package/dist/registry/post-card.json +1 -1
- package/dist/registry/products-page.json +1 -1
- package/dist/registry/related-posts-block.json +1 -1
- package/dist/registry/related-products-block.json +1 -1
- package/dist/registry/reset-password-page.json +19 -10
- package/package.json +1 -1
- package/template/bun.lock +2 -2
- package/template/package.json +2 -2
- package/template/vite.config.ts +65 -9
package/dist/index.js
CHANGED
|
@@ -259,7 +259,7 @@ For (2), check out our guide on configuring puppeteer at https://pptr.dev/guides
|
|
|
259
259
|
${X}`,RG0=Object.getOwnPropertyDescriptor(Function.prototype,"toString"),EG0=Object.getOwnPropertyDescriptor(Function.prototype.toString,"name"),TG0=(D,X,Z)=>{let J=Z===""?"":`with ${Z.trim()}() `,Y=NG0.bind(null,J,X.toString());Object.defineProperty(Y,"name",EG0);let{writable:Q,enumerable:$,configurable:K}=RG0;Object.defineProperty(D,"toString",{value:Y,writable:Q,enumerable:$,configurable:K})};function PL(D,X,{ignoreNonConfigurable:Z=!1}={}){let{name:J}=D;for(let Y of Reflect.ownKeys(X))LG0(D,X,Y,Z);return AG0(D,X),TG0(D,X,J),D}var NF=new WeakMap,s_=(D,X={})=>{if(typeof D!=="function")throw TypeError("Expected a function");let Z,J=0,Y=D.displayName||D.name||"<anonymous>",Q=function(...$){if(NF.set(Q,++J),J===1)Z=D.apply(this,$),D=void 0;else if(X.throw===!0)throw Error(`Function \`${Y}\` can only be called once`);return Z};return PL(Q,D),NF.set(Q,J),Q};s_.callCount=(D)=>{if(!NF.has(D))throw Error(`The given function \`${D.name}\` is not wrapped by the \`onetime\` package`);return NF.get(D)};var a_=s_;var VD=[];VD.push("SIGHUP","SIGINT","SIGTERM");if(process.platform!=="win32")VD.push("SIGALRM","SIGABRT","SIGVTALRM","SIGXCPU","SIGXFSZ","SIGUSR2","SIGTRAP","SIGSYS","SIGQUIT","SIGIOT");if(process.platform==="linux")VD.push("SIGIO","SIGPOLL","SIGPWR","SIGSTKFLT");var RF=(D)=>!!D&&typeof D==="object"&&typeof D.removeListener==="function"&&typeof D.emit==="function"&&typeof D.reallyExit==="function"&&typeof D.listeners==="function"&&typeof D.kill==="function"&&typeof D.pid==="number"&&typeof D.on==="function",wL=Symbol.for("signal-exit emitter"),SL=globalThis,IG0=Object.defineProperty.bind(Object);class t_{emitted={afterExit:!1,exit:!1};listeners={afterExit:[],exit:[]};count=0;id=Math.random();constructor(){if(SL[wL])return SL[wL];IG0(SL,wL,{value:this,writable:!1,enumerable:!1,configurable:!1})}on(D,X){this.listeners[D].push(X)}removeListener(D,X){let Z=this.listeners[D],J=Z.indexOf(X);if(J===-1)return;if(J===0&&Z.length===1)Z.length=0;else Z.splice(J,1)}emit(D,X,Z){if(this.emitted[D])return!1;this.emitted[D]=!0;let J=!1;for(let Y of this.listeners[D])J=Y(X,Z)===!0||J;if(D==="exit")J=this.emit("afterExit",X,Z)||J;return J}}class kL{}var jG0=(D)=>{return{onExit(X,Z){return D.onExit(X,Z)},load(){return D.load()},unload(){return D.unload()}}};class e_ extends kL{onExit(){return()=>{}}load(){}unload(){}}class Dk extends kL{#D=_L.platform==="win32"?"SIGINT":"SIGHUP";#X=new t_;#Z;#J;#Q;#Y={};#$=!1;constructor(D){super();this.#Z=D,this.#Y={};for(let X of VD)this.#Y[X]=()=>{let Z=this.#Z.listeners(X),{count:J}=this.#X,Y=D;if(typeof Y.__signal_exit_emitter__==="object"&&typeof Y.__signal_exit_emitter__.count==="number")J+=Y.__signal_exit_emitter__.count;if(Z.length===J){this.unload();let Q=this.#X.emit("exit",null,X),$=X==="SIGHUP"?this.#D:X;if(!Q)D.kill(D.pid,$)}};this.#Q=D.reallyExit,this.#J=D.emit}onExit(D,X){if(!RF(this.#Z))return()=>{};if(this.#$===!1)this.load();let Z=X?.alwaysLast?"afterExit":"exit";return this.#X.on(Z,D),()=>{if(this.#X.removeListener(Z,D),this.#X.listeners.exit.length===0&&this.#X.listeners.afterExit.length===0)this.unload()}}load(){if(this.#$)return;this.#$=!0,this.#X.count+=1;for(let D of VD)try{let X=this.#Y[D];if(X)this.#Z.on(D,X)}catch(X){}this.#Z.emit=(D,...X)=>{return this.#K(D,...X)},this.#Z.reallyExit=(D)=>{return this.#G(D)}}unload(){if(!this.#$)return;this.#$=!1,VD.forEach((D)=>{let X=this.#Y[D];if(!X)throw Error("Listener not defined for signal: "+D);try{this.#Z.removeListener(D,X)}catch(Z){}}),this.#Z.emit=this.#J,this.#Z.reallyExit=this.#Q,this.#X.count-=1}#G(D){if(!RF(this.#Z))return 0;return this.#Z.exitCode=D||0,this.#X.emit("exit",this.#Z.exitCode,null),this.#Q.call(this.#Z,this.#Z.exitCode)}#K(D,...X){let Z=this.#J;if(D==="exit"&&RF(this.#Z)){if(typeof X[0]==="number")this.#Z.exitCode=X[0];let J=Z.call(this.#Z,D,...X);return this.#X.emit("exit",this.#Z.exitCode,null),J}else return Z.call(this.#Z,D,...X)}}var _L=globalThis.process,{onExit:Xk,load:b31,unload:f31}=jG0(RF(_L)?new Dk(_L):new e_);var Zk=EF.stderr.isTTY?EF.stderr:EF.stdout.isTTY?EF.stdout:void 0,CG0=Zk?a_(()=>{Xk(()=>{Zk.write("\x1B[?25h")},{alwaysLast:!0})}):()=>{},Jk=CG0;var TF=!1,qZ={};qZ.show=(D=Yk.stderr)=>{if(!D.isTTY)return;TF=!1,D.write("\x1B[?25h")};qZ.hide=(D=Yk.stderr)=>{if(!D.isTTY)return;Jk(),TF=!0,D.write("\x1B[?25l")};qZ.toggle=(D,X)=>{if(D!==void 0)TF=D;if(TF)qZ.show(X);else qZ.hide(X)};var hL=qZ;var lQ=k1(xL(),1);import N4 from"node:process";function yL(){if(N4.platform!=="win32")return N4.env.TERM!=="linux";return Boolean(N4.env.CI)||Boolean(N4.env.WT_SESSION)||Boolean(N4.env.TERMINUS_SUBLIME)||N4.env.ConEmuTask==="{cmd::Cmder}"||N4.env.TERM_PROGRAM==="Terminus-Sublime"||N4.env.TERM_PROGRAM==="vscode"||N4.env.TERM==="xterm-256color"||N4.env.TERM==="alacritty"||N4.env.TERMINAL_EMULATOR==="JetBrains-JediTerm"}var wG0={info:C.blue("ℹ"),success:C.green("✔"),warning:C.yellow("⚠"),error:C.red("✖")},SG0={info:C.blue("i"),success:C.green("√"),warning:C.yellow("‼"),error:C.red("×")},_G0=yL()?wG0:SG0,cQ=_G0;function vL({onlyFirst:D=!1}={}){return new RegExp("(?:\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\u005C|\\u009C))|[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]",D?void 0:"g")}var kG0=vL();function dQ(D){if(typeof D!=="string")throw TypeError(`Expected a \`string\`, got \`${typeof D}\``);return D.replace(kG0,"")}function Gk(D){return D===161||D===164||D===167||D===168||D===170||D===173||D===174||D>=176&&D<=180||D>=182&&D<=186||D>=188&&D<=191||D===198||D===208||D===215||D===216||D>=222&&D<=225||D===230||D>=232&&D<=234||D===236||D===237||D===240||D===242||D===243||D>=247&&D<=250||D===252||D===254||D===257||D===273||D===275||D===283||D===294||D===295||D===299||D>=305&&D<=307||D===312||D>=319&&D<=322||D===324||D>=328&&D<=331||D===333||D===338||D===339||D===358||D===359||D===363||D===462||D===464||D===466||D===468||D===470||D===472||D===474||D===476||D===593||D===609||D===708||D===711||D>=713&&D<=715||D===717||D===720||D>=728&&D<=731||D===733||D===735||D>=768&&D<=879||D>=913&&D<=929||D>=931&&D<=937||D>=945&&D<=961||D>=963&&D<=969||D===1025||D>=1040&&D<=1103||D===1105||D===8208||D>=8211&&D<=8214||D===8216||D===8217||D===8220||D===8221||D>=8224&&D<=8226||D>=8228&&D<=8231||D===8240||D===8242||D===8243||D===8245||D===8251||D===8254||D===8308||D===8319||D>=8321&&D<=8324||D===8364||D===8451||D===8453||D===8457||D===8467||D===8470||D===8481||D===8482||D===8486||D===8491||D===8531||D===8532||D>=8539&&D<=8542||D>=8544&&D<=8555||D>=8560&&D<=8569||D===8585||D>=8592&&D<=8601||D===8632||D===8633||D===8658||D===8660||D===8679||D===8704||D===8706||D===8707||D===8711||D===8712||D===8715||D===8719||D===8721||D===8725||D===8730||D>=8733&&D<=8736||D===8739||D===8741||D>=8743&&D<=8748||D===8750||D>=8756&&D<=8759||D===8764||D===8765||D===8776||D===8780||D===8786||D===8800||D===8801||D>=8804&&D<=8807||D===8810||D===8811||D===8814||D===8815||D===8834||D===8835||D===8838||D===8839||D===8853||D===8857||D===8869||D===8895||D===8978||D>=9312&&D<=9449||D>=9451&&D<=9547||D>=9552&&D<=9587||D>=9600&&D<=9615||D>=9618&&D<=9621||D===9632||D===9633||D>=9635&&D<=9641||D===9650||D===9651||D===9654||D===9655||D===9660||D===9661||D===9664||D===9665||D>=9670&&D<=9672||D===9675||D>=9678&&D<=9681||D>=9698&&D<=9701||D===9711||D===9733||D===9734||D===9737||D===9742||D===9743||D===9756||D===9758||D===9792||D===9794||D===9824||D===9825||D>=9827&&D<=9829||D>=9831&&D<=9834||D===9836||D===9837||D===9839||D===9886||D===9887||D===9919||D>=9926&&D<=9933||D>=9935&&D<=9939||D>=9941&&D<=9953||D===9955||D===9960||D===9961||D>=9963&&D<=9969||D===9972||D>=9974&&D<=9977||D===9979||D===9980||D===9982||D===9983||D===10045||D>=10102&&D<=10111||D>=11094&&D<=11097||D>=12872&&D<=12879||D>=57344&&D<=63743||D>=65024&&D<=65039||D===65533||D>=127232&&D<=127242||D>=127248&&D<=127277||D>=127280&&D<=127337||D>=127344&&D<=127373||D===127375||D===127376||D>=127387&&D<=127404||D>=917760&&D<=917999||D>=983040&&D<=1048573||D>=1048576&&D<=1114109}function Wk(D){return D===12288||D>=65281&&D<=65376||D>=65504&&D<=65510}function Fk(D){return D>=4352&&D<=4447||D===8986||D===8987||D===9001||D===9002||D>=9193&&D<=9196||D===9200||D===9203||D===9725||D===9726||D===9748||D===9749||D>=9776&&D<=9783||D>=9800&&D<=9811||D===9855||D>=9866&&D<=9871||D===9875||D===9889||D===9898||D===9899||D===9917||D===9918||D===9924||D===9925||D===9934||D===9940||D===9962||D===9970||D===9971||D===9973||D===9978||D===9981||D===9989||D===9994||D===9995||D===10024||D===10060||D===10062||D>=10067&&D<=10069||D===10071||D>=10133&&D<=10135||D===10160||D===10175||D===11035||D===11036||D===11088||D===11093||D>=11904&&D<=11929||D>=11931&&D<=12019||D>=12032&&D<=12245||D>=12272&&D<=12287||D>=12289&&D<=12350||D>=12353&&D<=12438||D>=12441&&D<=12543||D>=12549&&D<=12591||D>=12593&&D<=12686||D>=12688&&D<=12773||D>=12783&&D<=12830||D>=12832&&D<=12871||D>=12880&&D<=42124||D>=42128&&D<=42182||D>=43360&&D<=43388||D>=44032&&D<=55203||D>=63744&&D<=64255||D>=65040&&D<=65049||D>=65072&&D<=65106||D>=65108&&D<=65126||D>=65128&&D<=65131||D>=94176&&D<=94180||D>=94192&&D<=94198||D>=94208&&D<=101589||D>=101631&&D<=101662||D>=101760&&D<=101874||D>=110576&&D<=110579||D>=110581&&D<=110587||D===110589||D===110590||D>=110592&&D<=110882||D===110898||D>=110928&&D<=110930||D===110933||D>=110948&&D<=110951||D>=110960&&D<=111355||D>=119552&&D<=119638||D>=119648&&D<=119670||D===126980||D===127183||D===127374||D>=127377&&D<=127386||D>=127488&&D<=127490||D>=127504&&D<=127547||D>=127552&&D<=127560||D===127568||D===127569||D>=127584&&D<=127589||D>=127744&&D<=127776||D>=127789&&D<=127797||D>=127799&&D<=127868||D>=127870&&D<=127891||D>=127904&&D<=127946||D>=127951&&D<=127955||D>=127968&&D<=127984||D===127988||D>=127992&&D<=128062||D===128064||D>=128066&&D<=128252||D>=128255&&D<=128317||D>=128331&&D<=128334||D>=128336&&D<=128359||D===128378||D===128405||D===128406||D===128420||D>=128507&&D<=128591||D>=128640&&D<=128709||D===128716||D>=128720&&D<=128722||D>=128725&&D<=128728||D>=128732&&D<=128735||D===128747||D===128748||D>=128756&&D<=128764||D>=128992&&D<=129003||D===129008||D>=129292&&D<=129338||D>=129340&&D<=129349||D>=129351&&D<=129535||D>=129648&&D<=129660||D>=129664&&D<=129674||D>=129678&&D<=129734||D===129736||D>=129741&&D<=129756||D>=129759&&D<=129770||D>=129775&&D<=129784||D>=131072&&D<=196605||D>=196608&&D<=262141}function hG0(D){if(!Number.isSafeInteger(D))throw TypeError(`Expected a code point, got \`${typeof D}\`.`)}function Vk(D,{ambiguousAsWide:X=!1}={}){if(hG0(D),Wk(D)||Fk(D)||X&&Gk(D))return 2;return 1}var zk=k1(qk(),1),xG0=new Intl.Segmenter,yG0=/^\p{Default_Ignorable_Code_Point}$/u;function bL(D,X={}){if(typeof D!=="string"||D.length===0)return 0;let{ambiguousIsNarrow:Z=!0,countAnsiEscapeCodes:J=!1}=X;if(!J)D=dQ(D);if(D.length===0)return 0;let Y=0,Q={ambiguousAsWide:!Z};for(let{segment:$}of xG0.segment(D)){let K=$.codePointAt(0);if(K<=31||K>=127&&K<=159)continue;if(K>=8203&&K<=8207||K===65279)continue;if(K>=768&&K<=879||K>=6832&&K<=6911||K>=7616&&K<=7679||K>=8400&&K<=8447||K>=65056&&K<=65071)continue;if(K>=55296&&K<=57343)continue;if(K>=65024&&K<=65039)continue;if(yG0.test($))continue;if(zk.default().test($)){Y+=2;continue}Y+=Vk(K,Q)}return Y}function fL({stream:D=process.stdout}={}){return Boolean(D&&D.isTTY&&process.env.TERM!=="dumb"&&!("CI"in process.env))}import Uk from"node:process";function gL(){let{env:D}=Uk,{TERM:X,TERM_PROGRAM:Z}=D;if(Uk.platform!=="win32")return X!=="linux";return Boolean(D.WT_SESSION)||Boolean(D.TERMINUS_SUBLIME)||D.ConEmuTask==="{cmd::Cmder}"||Z==="Terminus-Sublime"||Z==="vscode"||X==="xterm-256color"||X==="alacritty"||X==="rxvt-unicode"||X==="rxvt-unicode-256color"||D.TERMINAL_EMULATOR==="JetBrains-JediTerm"}import Q9 from"node:process";var vG0=3;class Bk{#D=0;start(){if(this.#D++,this.#D===1)this.#X()}stop(){if(this.#D<=0)throw Error("`stop` called more times than `start`");if(this.#D--,this.#D===0)this.#Z()}#X(){if(Q9.platform==="win32"||!Q9.stdin.isTTY)return;Q9.stdin.setRawMode(!0),Q9.stdin.on("data",this.#J),Q9.stdin.resume()}#Z(){if(!Q9.stdin.isTTY)return;Q9.stdin.off("data",this.#J),Q9.stdin.pause(),Q9.stdin.setRawMode(!1)}#J(D){if(D[0]===vG0)Q9.emit("SIGINT")}}var bG0=new Bk,uL=bG0;var fG0=k1(xL(),1);class Ok{#D=0;#X=!1;#Z=0;#J=-1;#Q=0;#Y;#$;#G;#K;#W;#V;#q;#H;#F;#U;#B;color;constructor(D){if(typeof D==="string")D={text:D};if(this.#Y={color:"cyan",stream:jF.stderr,discardStdin:!0,hideCursor:!0,...D},this.color=this.#Y.color,this.spinner=this.#Y.spinner,this.#W=this.#Y.interval,this.#G=this.#Y.stream,this.#V=typeof this.#Y.isEnabled==="boolean"?this.#Y.isEnabled:fL({stream:this.#G}),this.#q=typeof this.#Y.isSilent==="boolean"?this.#Y.isSilent:!1,this.text=this.#Y.text,this.prefixText=this.#Y.prefixText,this.suffixText=this.#Y.suffixText,this.indent=this.#Y.indent,jF.env.NODE_ENV==="test")this._stream=this.#G,this._isEnabled=this.#V,Object.defineProperty(this,"_linesToClear",{get(){return this.#D},set(X){this.#D=X}}),Object.defineProperty(this,"_frameIndex",{get(){return this.#J}}),Object.defineProperty(this,"_lineCount",{get(){return this.#Z}})}get indent(){return this.#H}set indent(D=0){if(!(D>=0&&Number.isInteger(D)))throw Error("The `indent` option must be an integer from 0 and up");this.#H=D,this.#M()}get interval(){return this.#W??this.#$.interval??100}get spinner(){return this.#$}set spinner(D){if(this.#J=-1,this.#W=void 0,typeof D==="object"){if(D.frames===void 0)throw Error("The given spinner must have a `frames` property");this.#$=D}else if(!gL())this.#$=lQ.default.line;else if(D===void 0)this.#$=lQ.default.dots;else if(D!=="default"&&lQ.default[D])this.#$=lQ.default[D];else throw Error(`There is no built-in spinner named '${D}'. See https://github.com/sindresorhus/cli-spinners/blob/main/spinners.json for a full list.`)}get text(){return this.#F}set text(D=""){this.#F=D,this.#M()}get prefixText(){return this.#U}set prefixText(D=""){this.#U=D,this.#M()}get suffixText(){return this.#B}set suffixText(D=""){this.#B=D,this.#M()}get isSpinning(){return this.#K!==void 0}#O(D=this.#U,X=" "){if(typeof D==="string"&&D!=="")return D+X;if(typeof D==="function")return D()+X;return""}#R(D=this.#B,X=" "){if(typeof D==="string"&&D!=="")return X+D;if(typeof D==="function")return X+D();return""}#M(){let D=this.#G.columns??80,X=this.#O(this.#U,"-"),Z=this.#R(this.#B,"-"),J=" ".repeat(this.#H)+X+"--"+this.#F+"--"+Z;this.#Z=0;for(let Y of dQ(J).split(`
|
|
260
260
|
`))this.#Z+=Math.max(1,Math.ceil(bL(Y,{countAnsiEscapeCodes:!0})/D))}get isEnabled(){return this.#V&&!this.#q}set isEnabled(D){if(typeof D!=="boolean")throw TypeError("The `isEnabled` option must be a boolean");this.#V=D}get isSilent(){return this.#q}set isSilent(D){if(typeof D!=="boolean")throw TypeError("The `isSilent` option must be a boolean");this.#q=D}frame(){let D=Date.now();if(this.#J===-1||D-this.#Q>=this.interval)this.#J=++this.#J%this.#$.frames.length,this.#Q=D;let{frames:X}=this.#$,Z=X[this.#J];if(this.color)Z=C[this.color](Z);let J=typeof this.#U==="string"&&this.#U!==""?this.#U+" ":"",Y=typeof this.text==="string"?" "+this.text:"",Q=typeof this.#B==="string"&&this.#B!==""?" "+this.#B:"";return J+Z+Y+Q}clear(){if(!this.#V||!this.#G.isTTY)return this;this.#G.cursorTo(0);for(let D=0;D<this.#D;D++){if(D>0)this.#G.moveCursor(0,-1);this.#G.clearLine(1)}if(this.#H||this.lastIndent!==this.#H)this.#G.cursorTo(this.#H);return this.lastIndent=this.#H,this.#D=0,this}render(){if(this.#q)return this;return this.clear(),this.#G.write(this.frame()),this.#D=this.#Z,this}start(D){if(D)this.text=D;if(this.#q)return this;if(!this.#V){if(this.text)this.#G.write(`- ${this.text}
|
|
261
261
|
`);return this}if(this.isSpinning)return this;if(this.#Y.hideCursor)hL.hide(this.#G);if(this.#Y.discardStdin&&jF.stdin.isTTY)this.#X=!0,uL.start();return this.render(),this.#K=setInterval(this.render.bind(this),this.interval),this}stop(){if(!this.#V)return this;if(clearInterval(this.#K),this.#K=void 0,this.#J=0,this.clear(),this.#Y.hideCursor)hL.show(this.#G);if(this.#Y.discardStdin&&jF.stdin.isTTY&&this.#X)uL.stop(),this.#X=!1;return this}succeed(D){return this.stopAndPersist({symbol:cQ.success,text:D})}fail(D){return this.stopAndPersist({symbol:cQ.error,text:D})}warn(D){return this.stopAndPersist({symbol:cQ.warning,text:D})}info(D){return this.stopAndPersist({symbol:cQ.info,text:D})}stopAndPersist(D={}){if(this.#q)return this;let X=D.prefixText??this.#U,Z=this.#O(X," "),J=D.symbol??" ",Y=D.text??this.text,$=typeof Y==="string"?(J?" ":"")+Y:"",K=D.suffixText??this.#B,G=this.#R(K," "),F=Z+J+$+G+`
|
|
262
|
-
`;return this.stop(),this.#G.write(F),this}}function E2(D){return new Ok(D)}var IZ=k1(r8(),1);import Q$ from"path";import{execSync as nV0}from"child_process";var tQ=k1(r8(),1);import XM from"path";var ZM="promake.json";async function T2(D){let X=XM.join(D,ZM);if(await tQ.default.pathExists(X))return tQ.default.readJson(X);return null}async function c9(D,X){let Z=XM.join(D,ZM);await tQ.default.writeJson(Z,X,{spaces:2})}async function JM(D){let X=XM.join(D,ZM);return tQ.default.pathExists(X)}function zD(){return{modulesPath:"src/modules",envFile:".env",modules:{}}}async function fF(D,X,Z){let J=await T2(D)||zD();if(!J.modules)J.modules={};J.modules[X]=Z,await c9(D,J)}async function YM(D,X){let Z=await T2(D);if(Z?.modules&&X in Z.modules)delete Z.modules[X],await c9(D,Z)}function gF(D,X,Z){if(Z==="default")return`${D.modulesPath||"src/modules"}/${X}`;return Z}var d9=k1(r8(),1);import E4 from"path";import{fileURLToPath as DV0}from"url";var XV0=DV0(import.meta.url),eQ=E4.dirname(XV0),D$={mode:process.env.PROMAKE_MODE||"local",remoteRegistryUrl:"https://raw.githubusercontent.com/anthropics/promake-registry/main",remoteTemplateUrl:"https://raw.githubusercontent.com/anthropics/promake-registry/main/template"},Hx=eQ.includes("src"),qx=Hx?E4.join(eQ,"..","..","dist","registry"):E4.join(eQ,"registry"),zx=Hx?E4.join(eQ,"..","..","template"):E4.join(eQ,"..","template");async function T4(D){if(D$.mode==="local"){let J=E4.join(qx,`${D}.json`);if(await d9.default.pathExists(J))return d9.default.readJson(J)}let X=`${D$.remoteRegistryUrl}/registry/${D}.json`,Z=await fetch(X);if(!Z.ok)throw Error(`Registry item "${D}" not found (status: ${Z.status})`);return Z.json()}async function Ux(){if(D$.mode==="local"){let Z=E4.join(qx,"index.json");if(await d9.default.pathExists(Z))return d9.default.readJson(Z)}let D=`${D$.remoteRegistryUrl}/registry/index.json`,X=await fetch(D);if(!X.ok)throw Error("Failed to fetch registry index");return X.json()}async function Bx(D){try{return(await T4(D)).usage||null}catch{return null}}async function ZV0(){return d9.default.pathExists(zx)}async function Ox(D,X,Z){let J=E4.relative(Z,D)||".";console.log(`[copyDirRecursive] 1. ensureDir: ${X} (rel: ${J})`),await d9.default.ensureDir(X),console.log(`[copyDirRecursive] 2. readdir: ${D}`);let Y=await d9.default.readdir(D,{withFileTypes:!0});console.log(`[copyDirRecursive] 3. entries count: ${Y.length} in ${J}`);for(let Q of Y){let $=E4.join(D,Q.name),K=E4.join(X,Q.name),G=E4.relative(Z,$);if(G.includes("node_modules")){console.log(`[copyDirRecursive] 4. SKIP node_modules: ${G}`);continue}if(Q.isDirectory())console.log(`[copyDirRecursive] 5. DIR recurse: ${G}`),await Ox($,K,Z);else console.log(`[copyDirRecursive] 6. COPY file: ${G}`),await d9.default.copyFile($,K),console.log(`[copyDirRecursive] 7. DONE file: ${G}`)}console.log(`[copyDirRecursive] 8. DIR complete: ${J}`)}async function Lx(D){console.log(`[copyTemplate] 1. START targetPath: ${D}`);let X=await ZV0();if(console.log(`[copyTemplate] 2. hasLocalTemplate: ${X}`),X){let Z=zx;console.log(`[copyTemplate] 3. templateRoot: ${Z}`),console.log("[copyTemplate] 4. calling copyDirRecursive..."),await Ox(Z,D,Z),console.log("[copyTemplate] 5. copyDirRecursive DONE");return}if(console.log("[copyTemplate] 6. no local template found"),D$.mode==="remote")throw Error("Remote template download not yet implemented. Please use local mode or ensure template/ exists.");throw Error("Template not found. Ensure cli/template/ exists.")}var l9=k1(r8(),1);import{execSync as JV0}from"child_process";import LZ from"path";function X$(D){if(l9.default.existsSync(LZ.join(D,"bun.lockb")))return"bun";if(l9.default.existsSync(LZ.join(D,"pnpm-lock.yaml")))return"pnpm";if(l9.default.existsSync(LZ.join(D,"yarn.lock")))return"yarn";if(l9.default.existsSync(LZ.join(D,"package-lock.json")))return"npm";return"npm"}function YV0(D,X,Z=!1){let J=X.join(" ");return{bun:`bun add ${Z?"-D ":""}${J}`,npm:`npm install ${Z?"--save-dev ":""}${J}`,yarn:`yarn add ${Z?"-D ":""}${J}`,pnpm:`pnpm add ${Z?"-D ":""}${J}`}[D]}async function QM(D,X,Z=!1){if(X.length===0)return;let J=X$(D),Y=YV0(J,X,Z);JV0(Y,{cwd:D,stdio:"inherit"})}var QV0=new Set(["react","react-dom","react-router","zustand","@tanstack/react-query","tailwindcss","@tailwindcss/vite","tailwind-merge","clsx","class-variance-authority","@radix-ui/react-accordion","@radix-ui/react-alert-dialog","@radix-ui/react-aspect-ratio","@radix-ui/react-avatar","@radix-ui/react-checkbox","@radix-ui/react-collapsible","@radix-ui/react-context-menu","@radix-ui/react-dialog","@radix-ui/react-dropdown-menu","@radix-ui/react-hover-card","@radix-ui/react-label","@radix-ui/react-menubar","@radix-ui/react-navigation-menu","@radix-ui/react-popover","@radix-ui/react-progress","@radix-ui/react-radio-group","@radix-ui/react-scroll-area","@radix-ui/react-select","@radix-ui/react-separator","@radix-ui/react-slider","@radix-ui/react-slot","@radix-ui/react-switch","@radix-ui/react-tabs","@radix-ui/react-toggle","@radix-ui/react-toggle-group","@radix-ui/react-tooltip","cmdk","embla-carousel-react","input-otp","react-day-picker","react-resizable-panels","recharts","sonner","vaul","react-hook-form","@hookform/resolvers","zod","i18next","react-i18next","i18next-browser-languagedetector","axios","date-fns","lucide-react","motion","next-themes","sql.js","@promakeai/customer-backend-client","@promakeai/inspector"]);async function Mx(D){let X=LZ.join(D,"package.json"),Z=new Set;if(!await l9.default.pathExists(X))return Z;let J=await l9.default.readJson(X),Y=J.dependencies||{},Q=J.devDependencies||{};for(let $ of Object.keys(Y))Z.add($);for(let $ of Object.keys(Q))Z.add($);return Z}function $M(D,X){return D.filter((Z)=>{let J=Z.split("@")[0]||Z.replace(/^@/,"").split("@")[0],Y=Z.startsWith("@")?"@"+J:J;if(QV0.has(Y))return!1;if(X.has(Y))return!1;return!0})}async function Ax(D,X){let Z=LZ.join(D,"package.json"),J=await l9.default.readJson(Z);J.name=X,await l9.default.writeJson(Z,J,{spaces:2})}var I4=[{name:"ecommerce",description:"E-commerce starter with cart, products, checkout",header:"header-ecommerce",footer:"footer",indexSections:["hero-cta","featured-products","category-section"],modules:["ecommerce-core","product-card","products-page","cart-page","checkout-page","order-confirmation-page","my-orders-page","cart-drawer","favorites-ecommerce-
|
|
262
|
+
`;return this.stop(),this.#G.write(F),this}}function E2(D){return new Ok(D)}var IZ=k1(r8(),1);import Q$ from"path";import{execSync as nV0}from"child_process";var tQ=k1(r8(),1);import XM from"path";var ZM="promake.json";async function T2(D){let X=XM.join(D,ZM);if(await tQ.default.pathExists(X))return tQ.default.readJson(X);return null}async function c9(D,X){let Z=XM.join(D,ZM);await tQ.default.writeJson(Z,X,{spaces:2})}async function JM(D){let X=XM.join(D,ZM);return tQ.default.pathExists(X)}function zD(){return{modulesPath:"src/modules",envFile:".env",modules:{}}}async function fF(D,X,Z){let J=await T2(D)||zD();if(!J.modules)J.modules={};J.modules[X]=Z,await c9(D,J)}async function YM(D,X){let Z=await T2(D);if(Z?.modules&&X in Z.modules)delete Z.modules[X],await c9(D,Z)}function gF(D,X,Z){if(Z==="default")return`${D.modulesPath||"src/modules"}/${X}`;return Z}var d9=k1(r8(),1);import E4 from"path";import{fileURLToPath as DV0}from"url";var XV0=DV0(import.meta.url),eQ=E4.dirname(XV0),D$={mode:process.env.PROMAKE_MODE||"local",remoteRegistryUrl:"https://raw.githubusercontent.com/anthropics/promake-registry/main",remoteTemplateUrl:"https://raw.githubusercontent.com/anthropics/promake-registry/main/template"},Hx=eQ.includes("src"),qx=Hx?E4.join(eQ,"..","..","dist","registry"):E4.join(eQ,"registry"),zx=Hx?E4.join(eQ,"..","..","template"):E4.join(eQ,"..","template");async function T4(D){if(D$.mode==="local"){let J=E4.join(qx,`${D}.json`);if(await d9.default.pathExists(J))return d9.default.readJson(J)}let X=`${D$.remoteRegistryUrl}/registry/${D}.json`,Z=await fetch(X);if(!Z.ok)throw Error(`Registry item "${D}" not found (status: ${Z.status})`);return Z.json()}async function Ux(){if(D$.mode==="local"){let Z=E4.join(qx,"index.json");if(await d9.default.pathExists(Z))return d9.default.readJson(Z)}let D=`${D$.remoteRegistryUrl}/registry/index.json`,X=await fetch(D);if(!X.ok)throw Error("Failed to fetch registry index");return X.json()}async function Bx(D){try{return(await T4(D)).usage||null}catch{return null}}async function ZV0(){return d9.default.pathExists(zx)}async function Ox(D,X,Z){let J=E4.relative(Z,D)||".";console.log(`[copyDirRecursive] 1. ensureDir: ${X} (rel: ${J})`),await d9.default.ensureDir(X),console.log(`[copyDirRecursive] 2. readdir: ${D}`);let Y=await d9.default.readdir(D,{withFileTypes:!0});console.log(`[copyDirRecursive] 3. entries count: ${Y.length} in ${J}`);for(let Q of Y){let $=E4.join(D,Q.name),K=E4.join(X,Q.name),G=E4.relative(Z,$);if(G.includes("node_modules")){console.log(`[copyDirRecursive] 4. SKIP node_modules: ${G}`);continue}if(Q.isDirectory())console.log(`[copyDirRecursive] 5. DIR recurse: ${G}`),await Ox($,K,Z);else console.log(`[copyDirRecursive] 6. COPY file: ${G}`),await d9.default.copyFile($,K),console.log(`[copyDirRecursive] 7. DONE file: ${G}`)}console.log(`[copyDirRecursive] 8. DIR complete: ${J}`)}async function Lx(D){console.log(`[copyTemplate] 1. START targetPath: ${D}`);let X=await ZV0();if(console.log(`[copyTemplate] 2. hasLocalTemplate: ${X}`),X){let Z=zx;console.log(`[copyTemplate] 3. templateRoot: ${Z}`),console.log("[copyTemplate] 4. calling copyDirRecursive..."),await Ox(Z,D,Z),console.log("[copyTemplate] 5. copyDirRecursive DONE");return}if(console.log("[copyTemplate] 6. no local template found"),D$.mode==="remote")throw Error("Remote template download not yet implemented. Please use local mode or ensure template/ exists.");throw Error("Template not found. Ensure cli/template/ exists.")}var l9=k1(r8(),1);import{execSync as JV0}from"child_process";import LZ from"path";function X$(D){if(l9.default.existsSync(LZ.join(D,"bun.lockb")))return"bun";if(l9.default.existsSync(LZ.join(D,"pnpm-lock.yaml")))return"pnpm";if(l9.default.existsSync(LZ.join(D,"yarn.lock")))return"yarn";if(l9.default.existsSync(LZ.join(D,"package-lock.json")))return"npm";return"npm"}function YV0(D,X,Z=!1){let J=X.join(" ");return{bun:`bun add ${Z?"-D ":""}${J}`,npm:`npm install ${Z?"--save-dev ":""}${J}`,yarn:`yarn add ${Z?"-D ":""}${J}`,pnpm:`pnpm add ${Z?"-D ":""}${J}`}[D]}async function QM(D,X,Z=!1){if(X.length===0)return;let J=X$(D),Y=YV0(J,X,Z);JV0(Y,{cwd:D,stdio:"inherit"})}var QV0=new Set(["react","react-dom","react-router","zustand","@tanstack/react-query","tailwindcss","@tailwindcss/vite","tailwind-merge","clsx","class-variance-authority","@radix-ui/react-accordion","@radix-ui/react-alert-dialog","@radix-ui/react-aspect-ratio","@radix-ui/react-avatar","@radix-ui/react-checkbox","@radix-ui/react-collapsible","@radix-ui/react-context-menu","@radix-ui/react-dialog","@radix-ui/react-dropdown-menu","@radix-ui/react-hover-card","@radix-ui/react-label","@radix-ui/react-menubar","@radix-ui/react-navigation-menu","@radix-ui/react-popover","@radix-ui/react-progress","@radix-ui/react-radio-group","@radix-ui/react-scroll-area","@radix-ui/react-select","@radix-ui/react-separator","@radix-ui/react-slider","@radix-ui/react-slot","@radix-ui/react-switch","@radix-ui/react-tabs","@radix-ui/react-toggle","@radix-ui/react-toggle-group","@radix-ui/react-tooltip","cmdk","embla-carousel-react","input-otp","react-day-picker","react-resizable-panels","recharts","sonner","vaul","react-hook-form","@hookform/resolvers","zod","i18next","react-i18next","i18next-browser-languagedetector","axios","date-fns","lucide-react","motion","next-themes","sql.js","@promakeai/customer-backend-client","@promakeai/inspector"]);async function Mx(D){let X=LZ.join(D,"package.json"),Z=new Set;if(!await l9.default.pathExists(X))return Z;let J=await l9.default.readJson(X),Y=J.dependencies||{},Q=J.devDependencies||{};for(let $ of Object.keys(Y))Z.add($);for(let $ of Object.keys(Q))Z.add($);return Z}function $M(D,X){return D.filter((Z)=>{let J=Z.split("@")[0]||Z.replace(/^@/,"").split("@")[0],Y=Z.startsWith("@")?"@"+J:J;if(QV0.has(Y))return!1;if(X.has(Y))return!1;return!0})}async function Ax(D,X){let Z=LZ.join(D,"package.json"),J=await l9.default.readJson(Z);J.name=X,await l9.default.writeJson(Z,J,{spaces:2})}var I4=[{name:"ecommerce",description:"E-commerce starter with cart, products, checkout",header:"header-ecommerce",footer:"footer",indexSections:["hero-cta","featured-products","category-section"],modules:["api","ecommerce-core","product-card","products-page","product-detail-page","cart-page","checkout-page","order-confirmation-page","my-orders-page","cart-drawer","favorites-ecommerce-page","auth-core","login-page","register-page","forgot-password-page","reset-password-page"],theme:{preset:"blue",radius:"medium",shadow:"small",maxWidth:"6xl"}},{name:"blog",description:"Blog starter with posts, categories, favorites",header:"header-simple",footer:"footer-minimal",indexSections:["hero","blog-section"],modules:["blog-core","post-card","post-detail-page","blog-list-page"],theme:{preset:"rose",radius:"medium",shadow:"subtle",font:"nunito"}},{name:"portfolio",description:"Portfolio starter with skills, services",header:"header-minimal",footer:"footer-minimal",indexSections:["hero-profile","feature-section","cta-section"],modules:["skill-card","service-card"],theme:{preset:"slate",radius:"medium",shadow:"subtle",font:"raleway"}},{name:"auth",description:"Authentication starter with login, register, forgot & reset password",modules:["auth-core","api","login-page","register-page","forgot-password-page","reset-password-page"],theme:{preset:"zinc"}},{name:"empty",description:"Minimal setup without header, footer, or sections",modules:[],theme:{preset:"zinc"}}];var uF=["api","auth","db","animations"];import HM from"path";import px from"fs/promises";async function KM(D,X=!1){if(await JM(D)&&!X)return!1;let Z=zD();return await c9(D,Z),!0}var Nx=new $5("init").description("Initialize promake.json in current directory").option("-f, --force","Overwrite existing config").action(async(D)=>{let X=process.cwd();if(await JM(X)&&!D.force){console.log(C.yellow("promake.json already exists.")),console.log(C.dim("Use --force to overwrite."));return}await KM(X,D.force),console.log(C.green("Created promake.json")),console.log(""),console.log("You can now add modules:"),console.log(C.cyan(" npx promake add <module-name>"))});var d5=k1(r8(),1);import Z$ from"path";async function Ex(D,X,Z){let J=await d5.default.pathExists(X);switch(Z){case"overwrite":return await d5.default.ensureDir(Z$.dirname(X)),await d5.default.writeFile(X,D,"utf-8"),{action:J?"updated":"created",path:X};case"stop":if(J)return{action:"skipped",path:X,reason:"file exists"};return await d5.default.ensureDir(Z$.dirname(X)),await d5.default.writeFile(X,D,"utf-8"),{action:"created",path:X};case"skip":return{action:"skipped",path:X,reason:"skip behavior"};case"merge":if(X.endsWith(".json"))return $V0(D,X);if(X.endsWith(".css"))return KV0(D,X);return{action:"skipped",path:X,reason:"no merge strategy"};case"prompt":if(J)return{action:"skipped",path:X,reason:"file exists"};return await d5.default.ensureDir(Z$.dirname(X)),await d5.default.writeFile(X,D,"utf-8"),{action:"created",path:X};default:return{action:"skipped",path:X,reason:"unknown behavior"}}}async function $V0(D,X){if(!await d5.default.pathExists(X))return await d5.default.ensureDir(Z$.dirname(X)),await d5.default.writeFile(X,D,"utf-8"),{action:"created",path:X};try{let J=await d5.default.readJson(X),Y=JSON.parse(D),Q=Tx(J,Y);return await d5.default.writeJson(X,Q,{spaces:2}),{action:"merged",path:X}}catch(J){return{action:"skipped",path:X,reason:"merge failed"}}}async function KV0(D,X){if(!await d5.default.pathExists(X))return await d5.default.ensureDir(Z$.dirname(X)),await d5.default.writeFile(X,D,"utf-8"),{action:"created",path:X};try{let J=await d5.default.readFile(X,"utf-8");if(!J.includes(D.trim()))return await d5.default.writeFile(X,J+`
|
|
263
263
|
`+D,"utf-8"),{action:"merged",path:X};return{action:"skipped",path:X,reason:"content already exists"}}catch(J){return{action:"skipped",path:X,reason:"merge failed"}}}function Tx(D,X){let Z={...D};for(let J in X)if(Object.prototype.hasOwnProperty.call(X,J)){let Y=X[J],Q=Z[J];if(!(J in Z))Z[J]=Y;else if(Rx(Y)&&Rx(Q))Z[J]=Tx(Q,Y)}return Z}function Rx(D){return Boolean(D&&typeof D==="object"&&!Array.isArray(D))}async function Ix(D){await d5.default.remove(D)}async function j4(D){return d5.default.pathExists(D)}async function jx(D){return d5.default.readFile(D,"utf-8")}var GM={"registry:store":"overwrite","registry:hook":"overwrite","registry:service":"overwrite","registry:type":"overwrite","registry:context":"stop","registry:page":"stop","registry:component":"overwrite","registry:block":"overwrite","registry:ui":"skip","registry:lang":"merge","registry:lib":"overwrite","registry:style":"merge","registry:module":"stop","registry:file":"prompt","registry:index":"overwrite"},GV0=["accordion","alert","alert-dialog","aspect-ratio","avatar","badge","breadcrumb","button","calendar","card","carousel","chart","checkbox","collapsible","command","context-menu","dialog","drawer","dropdown-menu","form","hover-card","input","input-otp","label","menubar","navigation-menu","pagination","popover","progress","radio-group","resizable","scroll-area","select","separator","sheet","sidebar","skeleton","slider","sonner","switch","table","tabs","textarea","toast","toggle","toggle-group","tooltip"];function Cx(D){return GV0.includes(D)}function Px(D){let X=[],Z=[];for(let J of D)if(Cx(J))X.push(J);else Z.push(J);return{shadcn:X,promake:Z}}var cF=k1(r8(),1),vx=k1(yx(),1);import hV0 from"path";async function bx(D,X,Z){let J=hV0.join(D,X),Y="";if(await cF.default.pathExists(J))Y=await cF.default.readFile(J,"utf-8");let Q=vx.default.parse(Y),$=[],K=[];for(let[G,F]of Object.entries(Z))if(!(G in Q))K.push(`${G}="${F}"`),$.push(G);if(K.length>0){let G=Y.trim()?`
|
|
264
264
|
|
|
265
265
|
`:"";Y=Y.trim()+G+K.join(`
|
|
@@ -353,7 +353,7 @@ ${J.usage}
|
|
|
353
353
|
`);if(Z+=`
|
|
354
354
|
|
|
355
355
|
`,Z+=`interface LayoutProps {
|
|
356
|
-
children
|
|
356
|
+
children?: ReactNode;
|
|
357
357
|
}
|
|
358
358
|
|
|
359
359
|
export function Layout({ children }: LayoutProps) {
|
|
@@ -494,7 +494,7 @@ constants.json (full)`)),console.log(C.dim(JSON.stringify(B.constantsConfig,null
|
|
|
494
494
|
`)),D6("promake create <name>","Create new project"),D6("promake add <modules...>","Add modules"),D6("promake add <page> --module-name <name>","Add page with custom name"),D6("promake remove <modules...>","Remove modules"),D6("promake sync","Sync from promake.json"),D6("promake theme --list","List theme options"),D6("promake theme --preset <name>","Apply color preset"),D6("promake list","List available modules"),D6("promake doctor","Check project health"),D6("promake doctor --runtime","Include runtime checks"),D6("promake discover --json","Project info for AI"),D6("promake seo","Sync SEO meta tags"),D6("promake usage","Show this guide"),D6("promake <command> --help","Command-specific help"),console.log()}var fr=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 T4(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 s91 from"path";var vK=k1(r8(),1);import{execSync as wx0}from"child_process";import xq from"path";async function rE(D,X=!1){let Z=Date.now(),J=[],Y=xq.join(D,"tsconfig.json");if(!await vK.default.pathExists(Y))return{name:"TypeScript",status:"skip",severity:"info",duration:Date.now()-Z,items:[],summary:"No tsconfig.json found"};let Q=xq.join(D,"package.json"),$=!1;if(await vK.default.pathExists(Q))$=!!(await vK.default.readJson(Q)).scripts?.check;let K=X$(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=wx0(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(...Sx0(W,D)),{name:"TypeScript",status:"fail",severity:"error",duration:Date.now()-Z,items:J,summary:`${J.length} type error(s)`}}}function Sx0(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:xq.isAbsolute(G)?xq.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 oD=k1(r8(),1);import{execSync as _x0}from"child_process";import bK from"path";var kx0=["eslint.config.js","eslint.config.mjs","eslint.config.cjs",".eslintrc.js",".eslintrc.cjs",".eslintrc.json",".eslintrc.yml",".eslintrc.yaml",".eslintrc"];async function hx0(D){for(let Z of kx0)if(await oD.default.pathExists(bK.join(D,Z)))return!0;let X=bK.join(D,"package.json");if(await oD.default.pathExists(X))try{if((await oD.default.readJson(X)).eslintConfig)return!0}catch{}return!1}async function sE(D,X=!1,Z=!1){let J=Date.now();if(!await hx0(D))return{name:"ESLint",status:"skip",severity:"warning",duration:Date.now()-J,items:[],summary:"No ESLint config found"};let Y=bK.join(D,"package.json"),Q=!1;if(await oD.default.pathExists(Y))Q=!!(await oD.default.readJson(Y)).scripts?.lint;let $=X$(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=_x0(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=xx0(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 xx0(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(`
|
|
495
495
|
`);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:bK.isAbsolute(Q)?bK.relative(X,Q):Q,line:parseInt(F[1]),column:parseInt(F[2]),code:F[5]||void 0,message:F[4].trim()})}return Z}var yq=k1(r8(),1);import{execSync as yx0}from"child_process";import fK from"path";async function aE(D){let X=Date.now(),Z=["vite.config.ts","vite.config.js","vite.config.mjs"],J=!1;for(let Y of Z)if(await yq.default.pathExists(fK.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{yx0("npx vite build 2>&1",{cwd:D,encoding:"utf-8",stdio:"pipe",env:{...process.env,NODE_ENV:"production"}});let Y=fK.join(D,"dist");if(await yq.default.pathExists(Y))await yq.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||"",$=vx0(Q,D);return{name:"Build",status:"fail",severity:"error",duration:Date.now()-X,items:$.length>0?$:[{message:"Build failed"}],summary:"Build failed"}}}function vx0(D,X){let Z=[],J=D.split(`
|
|
496
496
|
`),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:fK.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:fK.isAbsolute(F[1]||F[2])?fK.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 lS=k1(r8(),1);import DF from"path";var v91=[process.env["PROGRAMFILES(X86)"]&&DF.join(process.env["PROGRAMFILES(X86)"],"Google","Chrome","Application","chrome.exe"),process.env.PROGRAMFILES&&DF.join(process.env.PROGRAMFILES,"Google","Chrome","Application","chrome.exe"),process.env.LOCALAPPDATA&&DF.join(process.env.LOCALAPPDATA,"Google","Chrome","Application","chrome.exe"),process.env["PROGRAMFILES(X86)"]&&DF.join(process.env["PROGRAMFILES(X86)"],"Microsoft","Edge","Application","msedge.exe"),process.env.PROGRAMFILES&&DF.join(process.env.PROGRAMFILES,"Microsoft","Edge","Application","msedge.exe")].filter(Boolean),b91=["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome","/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge","/Applications/Chromium.app/Contents/MacOS/Chromium"],f91=["/usr/bin/google-chrome","/usr/bin/google-chrome-stable","/usr/bin/chromium-browser","/usr/bin/chromium","/snap/bin/chromium"];async function v$0(){let D=await g91();if(!D)return null;try{return{type:"chrome",browser:await(await Promise.resolve().then(() => (y$0(),x$0))).default.launch({executablePath:D,headless:!0,args:["--no-sandbox","--disable-setuid-sandbox","--disable-gpu"]})}}catch{return null}}async function g91(){if(process.env.CHROME_PATH){if(await lS.default.pathExists(process.env.CHROME_PATH))return process.env.CHROME_PATH}let D;switch(process.platform){case"win32":D=v91;break;case"darwin":D=b91;break;default:D=f91}for(let X of D)if(await lS.default.pathExists(X))return X;return null}async function b$0(D){try{await D.browser.close()}catch{}}async function pS(D,X,Z="/"){let J=Date.now();if(!await u91(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 v$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 U=H.location();$.push({type:q==="warning"?"warning":"console",message:H.text(),fileName:U?.url,lineNumber:U?.lineNumber,columnNumber:U?.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 m91(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 b$0(Q)}let F=c91($);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 u91(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 m91(D){return new Promise((X)=>setTimeout(X,D))}function c91(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(`
|
|
497
|
-
`)}})}function iS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${r91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)d91(Z,X);console.log(C.dim("─".repeat(40))),o91(D)}function d91(D,X){let Z=i91(D.status),J=n91(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 $)l91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function l91(D,X){let Z=X==="error"?C.red:C.yellow,J=p91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function p91(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 i91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function n91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function o91(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 r91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var f$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(()=>rE(X,$));if(D.lint!==!1)F.push(()=>sE(X,D.fix,$));if(D.build!==!1)F.push(()=>aE(X));if(D.runtime)F.push(()=>pS(Z,J,Y));let W=[];try{if(W=await a91(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:s91.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 iS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function a91(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 g$0={name:"@promakeai/cli",version:"0.
|
|
497
|
+
`)}})}function iS(D,X){console.log(""),console.log(C.bold("Health Check")+C.dim(` (${r91(D.duration)})`)),console.log(C.dim("─".repeat(40)));for(let Z of D.checks)d91(Z,X);console.log(C.dim("─".repeat(40))),o91(D)}function d91(D,X){let Z=i91(D.status),J=n91(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 $)l91(G,D.severity);if(K>0)console.log(C.dim(` ... +${K} more (--verbose)`))}function l91(D,X){let Z=X==="error"?C.red:C.yellow,J=p91(D),Y=J?C.cyan(J)+" ":"";console.log(` ${Z("›")} ${Y}${D.message}`)}function p91(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 i91(D){switch(D){case"pass":return C.green("✓");case"fail":return C.red("✗");case"skip":return C.dim("○");default:return C.dim("?")}}function n91(D,X){if(D==="fail")return X==="error"?C.red:C.yellow;if(D==="pass")return C.green;return C.dim}function o91(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 r91(D){if(D<1000)return`${D}ms`;return`${(D/1000).toFixed(1)}s`}var f$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(()=>rE(X,$));if(D.lint!==!1)F.push(()=>sE(X,D.fix,$));if(D.build!==!1)F.push(()=>aE(X));if(D.runtime)F.push(()=>pS(Z,J,Y));let W=[];try{if(W=await a91(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:s91.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 iS(V,D.verbose||!1);if(V.summary.errors>0)process.exit(1)});async function a91(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 g$0={name:"@promakeai/cli",version:"0.4.0",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",dotenv:"^17.2.3","fs-extra":"^11.3.3",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 P8=new $5;P8.name("promake").description(`Modular React template CLI - Build React apps with pre-built components
|
|
498
498
|
|
|
499
499
|
Quick Start:
|
|
500
500
|
$ promake create my-app
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"path": "auth-core/use-auth.ts",
|
|
28
28
|
"type": "registry:hook",
|
|
29
29
|
"target": "$modules$/auth-core/use-auth.ts",
|
|
30
|
-
"content": "import { useCallback, useEffect, useRef } from \"react\";\r\nimport {\r\n useAuthStore,\r\n type User,\r\n type AuthTokens,\r\n} from \"./auth-store\";\r\nimport { customerClient } from \"@/modules/api\";\r\n\r\n// Refresh token 1 minute before expiry\r\nconst REFRESH_BUFFER_MS = 60 * 1000;\r\n\r\nexport function useAuth() {\r\n const {\r\n user,\r\n tokens,\r\n isAuthenticated,\r\n setAuth,\r\n updateTokens,\r\n clearAuth,\r\n isTokenExpired,\r\n getTimeUntilExpiry,\r\n } = useAuthStore();\r\n\r\n const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const isRefreshingRef = useRef(false);\r\n\r\n // Refresh token using the refresh token\r\n const refreshAccessToken = useCallback(async (): Promise<boolean> => {\r\n const currentTokens = useAuthStore.getState().tokens;\r\n\r\n // Don't refresh if no refresh token exists\r\n if (!currentTokens?.refreshToken || isRefreshingRef.current) {\r\n console.log(\"⚠️ No refresh token available, skipping refresh\");\r\n return false;\r\n }\r\n\r\n isRefreshingRef.current = true;\r\n\r\n try {\r\n //
|
|
30
|
+
"content": "import { useCallback, useEffect, useRef } from \"react\";\r\nimport {\r\n useAuthStore,\r\n type User,\r\n type AuthTokens,\r\n} from \"./auth-store\";\r\nimport { customerClient } from \"@/modules/api\";\r\n\r\n// Refresh token 1 minute before expiry\r\nconst REFRESH_BUFFER_MS = 60 * 1000;\r\n\r\nexport function useAuth() {\r\n const {\r\n user,\r\n tokens,\r\n isAuthenticated,\r\n setAuth,\r\n updateTokens,\r\n clearAuth,\r\n isTokenExpired,\r\n getTimeUntilExpiry,\r\n } = useAuthStore();\r\n\r\n const refreshTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const isRefreshingRef = useRef(false);\r\n\r\n // Refresh token using the refresh token\r\n const refreshAccessToken = useCallback(async (): Promise<boolean> => {\r\n const currentTokens = useAuthStore.getState().tokens;\r\n\r\n // Don't refresh if no refresh token exists\r\n if (!currentTokens?.refreshToken || isRefreshingRef.current) {\r\n console.log(\"⚠️ No refresh token available, skipping refresh\");\r\n return false;\r\n }\r\n\r\n isRefreshingRef.current = true;\r\n\r\n try {\r\n // Use the typed refreshToken method\r\n const response = await customerClient.auth.refreshToken({\r\n refreshToken: currentTokens.refreshToken,\r\n });\r\n\r\n const { accessToken, refreshToken, expiresIn } = response;\r\n\r\n // Validate response has required data\r\n if (!accessToken) {\r\n console.error(\"❌ Refresh response missing accessToken\");\r\n return false;\r\n }\r\n\r\n const newTokens: AuthTokens = {\r\n accessToken,\r\n refreshToken: refreshToken || currentTokens.refreshToken,\r\n idToken: currentTokens.idToken, // Preserve existing idToken\r\n encryptionKey: currentTokens.encryptionKey, // Preserve existing encryptionKey\r\n expiresAt: expiresIn ? Date.now() + expiresIn * 1000 : undefined,\r\n };\r\n\r\n customerClient.setToken(accessToken);\r\n updateTokens(newTokens);\r\n\r\n console.log(\"✅ Token refreshed successfully\");\r\n return true;\r\n } catch (error) {\r\n console.error(\"❌ Token refresh failed:\", error);\r\n // DON'T clear auth on refresh failure - just return false\r\n // User can still use their existing token until it expires\r\n return false;\r\n } finally {\r\n isRefreshingRef.current = false;\r\n }\r\n }, [updateTokens]);\r\n\r\n // Schedule automatic token refresh\r\n const scheduleTokenRefresh = useCallback(() => {\r\n // Clear any existing timeout\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n refreshTimeoutRef.current = null;\r\n }\r\n\r\n const timeUntilExpiry = getTimeUntilExpiry();\r\n\r\n // Only schedule if we have an expiry time and a refresh token\r\n if (timeUntilExpiry === null || !tokens?.refreshToken) {\r\n return;\r\n }\r\n\r\n // Calculate when to refresh (REFRESH_BUFFER_MS before expiry)\r\n const refreshIn = Math.max(timeUntilExpiry - REFRESH_BUFFER_MS, 0);\r\n\r\n // Don't schedule if expiry is too far in the future (> 24 hours)\r\n if (refreshIn > 24 * 60 * 60 * 1000) {\r\n return;\r\n }\r\n\r\n refreshTimeoutRef.current = setTimeout(async () => {\r\n const success = await refreshAccessToken();\r\n if (success) {\r\n // Reschedule for the new token\r\n scheduleTokenRefresh();\r\n }\r\n }, refreshIn);\r\n }, [getTimeUntilExpiry, tokens?.refreshToken, refreshAccessToken]);\r\n\r\n // Sync token with API client and set up refresh on mount and token changes\r\n useEffect(() => {\r\n if (tokens?.accessToken) {\r\n console.log(\"🔑 Setting token in API client\");\r\n customerClient.setToken(tokens.accessToken);\r\n\r\n // Only try to refresh if we have a refresh token AND token is expired\r\n if (isTokenExpired() && tokens.refreshToken) {\r\n console.log(\"⏰ Token expired, attempting refresh...\");\r\n refreshAccessToken().then((success) => {\r\n if (success) {\r\n scheduleTokenRefresh();\r\n } else {\r\n console.log(\"⚠️ Refresh failed, but keeping existing token\");\r\n }\r\n });\r\n } else if (tokens.refreshToken) {\r\n // Only schedule refresh if we have a refresh token\r\n scheduleTokenRefresh();\r\n }\r\n } else if (tokens && Object.keys(tokens).length === 0) {\r\n // tokens is empty object {} - this shouldn't happen, log it\r\n console.warn(\"⚠️ Tokens object is empty, this may indicate a bug\");\r\n } else {\r\n customerClient.setToken(null);\r\n }\r\n\r\n // Cleanup timeout on unmount\r\n return () => {\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n }\r\n };\r\n }, [\r\n tokens?.accessToken,\r\n tokens?.refreshToken,\r\n isTokenExpired,\r\n refreshAccessToken,\r\n scheduleTokenRefresh,\r\n ]);\r\n\r\n // Set up axios interceptor for 401 responses (token expired during request)\r\n useEffect(() => {\r\n const interceptorId = customerClient.axios.interceptors.response.use(\r\n (response) => response,\r\n async (error) => {\r\n const originalRequest = error.config;\r\n\r\n // Skip refresh for auth endpoints to prevent infinite loops\r\n const isAuthEndpoint = originalRequest?.url?.includes(\"/auth/\");\r\n\r\n // If we get a 401 and haven't retried yet, try to refresh\r\n if (\r\n error.response?.status === 401 &&\r\n !originalRequest._retry &&\r\n tokens?.refreshToken &&\r\n !isAuthEndpoint\r\n ) {\r\n originalRequest._retry = true;\r\n\r\n const success = await refreshAccessToken();\r\n if (success) {\r\n // Retry the original request with new token\r\n const newTokens = useAuthStore.getState().tokens;\r\n if (newTokens?.accessToken) {\r\n originalRequest.headers.Authorization = `Bearer ${newTokens.accessToken}`;\r\n return customerClient.axios(originalRequest);\r\n }\r\n }\r\n }\r\n\r\n return Promise.reject(error);\r\n },\r\n );\r\n\r\n return () => {\r\n customerClient.axios.interceptors.response.eject(interceptorId);\r\n };\r\n }, [tokens?.refreshToken, refreshAccessToken]);\r\n\r\n const login = useCallback(async (username: string, password: string) => {\r\n const response = await customerClient.auth.login({ username, password });\r\n\r\n console.log(\"🔐 Login response:\", response);\r\n console.log(\"🔐 accessToken:\", response.accessToken);\r\n console.log(\"🔐 refreshToken:\", response.refreshToken);\r\n console.log(\"🔐 encryptionKey:\", response.encryptionKey);\r\n\r\n const newTokens: AuthTokens = {\r\n accessToken: response.accessToken,\r\n refreshToken: response.refreshToken,\r\n idToken: response.idToken,\r\n encryptionKey: response.encryptionKey,\r\n expiresAt: response.expiresIn\r\n ? Date.now() + response.expiresIn * 1000\r\n : undefined,\r\n };\r\n\r\n console.log(\"🔐 newTokens object:\", newTokens);\r\n\r\n const newUser: User = {\r\n username,\r\n };\r\n\r\n customerClient.setToken(newTokens.accessToken);\r\n setAuth(newUser, newTokens);\r\n\r\n console.log(\r\n \"🔐 Auth set complete, checking store:\",\r\n useAuthStore.getState().tokens,\r\n );\r\n }, []);\r\n\r\n const register = useCallback(\r\n async (username: string, email: string, password: string) => {\r\n await customerClient.auth.register({ username, email, password });\r\n },\r\n [],\r\n );\r\n\r\n const confirmEmail = useCallback(async (username: string, code: string) => {\r\n await customerClient.auth.confirm({ username, code });\r\n }, []);\r\n\r\n const forgotPassword = useCallback(async (username: string) => {\r\n await customerClient.auth.forgotPassword({ username });\r\n }, []);\r\n\r\n const resetPassword = useCallback(\r\n async (username: string, code: string, newPassword: string) => {\r\n await customerClient.auth.resetPassword({ username, code, newPassword });\r\n },\r\n [],\r\n );\r\n\r\n const logout = useCallback(() => {\r\n // Clear any scheduled refresh\r\n if (refreshTimeoutRef.current) {\r\n clearTimeout(refreshTimeoutRef.current);\r\n refreshTimeoutRef.current = null;\r\n }\r\n\r\n customerClient.setToken(null);\r\n clearAuth();\r\n }, [clearAuth]);\r\n\r\n return {\r\n user,\r\n token: tokens?.accessToken ?? null,\r\n tokens,\r\n isAuthenticated,\r\n api: customerClient,\r\n login,\r\n register,\r\n confirmEmail,\r\n forgotPassword,\r\n resetPassword,\r\n logout,\r\n refreshAccessToken,\r\n };\r\n}\r\n"
|
|
31
31
|
}
|
|
32
32
|
],
|
|
33
33
|
"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: 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"
|
|
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\" data-db-table=\"blog_categories\" data-db-id={category.id || category.slug}>\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 <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\n <PostCard post={post} layout={viewMode} />\n </div>\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",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"path": "blog-section/blog-section.tsx",
|
|
22
22
|
"type": "registry:component",
|
|
23
23
|
"target": "$modules$/blog-section/blog-section.tsx",
|
|
24
|
-
"content": "import { Link } from \"react-router\";\r\nimport { ArrowRight } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardFooter,\r\n CardHeader,\r\n} from \"@/components/ui/card\";\r\nimport { useRecentPosts } from \"@/modules/blog-core\";\r\nimport type { Post } from \"@/modules/blog-core/types\";\r\n\r\ninterface BlogSectionProps {\r\n posts?: Post[];\r\n loading?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function BlogSection({\r\n posts: propPosts,\r\n loading: propLoading,\r\n className,\r\n}: BlogSectionProps) {\r\n const { t } = useTranslation(\"blog-section\");\r\n const { posts: hookPosts, loading: hookLoading } = useRecentPosts(3);\r\n\r\n const posts = propPosts ?? hookPosts;\r\n const loading = propLoading ?? hookLoading;\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <Badge variant=\"secondary\" className=\"mb-4\">\r\n {t(\"tagline\", \"Latest Updates\")}\r\n </Badge>\r\n <h2 className=\"text-3xl font-bold md:text-4xl lg:text-5xl mb-4\">\r\n {t(\"title\", \"From Our Blog\")}\r\n </h2>\r\n <p className=\"text-muted-foreground max-w-2xl mx-auto mb-6\">\r\n {t(\r\n \"subtitle\",\r\n \"Discover the latest trends, tips, and insights from our team of experts.\"\r\n )}\r\n </p>\r\n <Button variant=\"link\" asChild>\r\n <Link to=\"/blog\">\r\n {t(\"viewAll\", \"View all articles\")}\r\n <ArrowRight className=\"ml-2 h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n </div>\r\n\r\n {/* Posts Grid */}\r\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto\">\r\n {loading ? (\r\n [...Array(3)].map((_, i) => (\r\n <Card key={i} className=\"overflow-hidden p-0 animate-pulse\">\r\n <div className=\"aspect-video bg-muted\"></div>\r\n <CardHeader className=\"pt-6 pb-2\">\r\n <div className=\"flex items-center gap-2 mb-2\">\r\n <div className=\"h-5 w-16 bg-muted rounded\"></div>\r\n <div className=\"h-4 w-20 bg-muted rounded\"></div>\r\n </div>\r\n <div className=\"h-6 w-3/4 bg-muted rounded\"></div>\r\n </CardHeader>\r\n <CardContent className=\"py-0\">\r\n <div className=\"h-4 w-full bg-muted rounded mb-2\"></div>\r\n <div className=\"h-4 w-2/3 bg-muted rounded\"></div>\r\n </CardContent>\r\n <CardFooter className=\"pb-2\">\r\n <div className=\"h-4 w-24 bg-muted rounded\"></div>\r\n </CardFooter>\r\n </Card>\r\n ))\r\n ) : (\r\n posts.map((post) => (\r\n <
|
|
24
|
+
"content": "import { Link } from \"react-router\";\r\nimport { ArrowRight } from \"lucide-react\";\r\nimport { useTranslation } from \"react-i18next\";\r\nimport { cn } from \"@/lib/utils\";\r\nimport { Badge } from \"@/components/ui/badge\";\r\nimport { Button } from \"@/components/ui/button\";\r\nimport {\r\n Card,\r\n CardContent,\r\n CardFooter,\r\n CardHeader,\r\n} from \"@/components/ui/card\";\r\nimport { useRecentPosts } from \"@/modules/blog-core\";\r\nimport type { Post } from \"@/modules/blog-core/types\";\r\n\r\ninterface BlogSectionProps {\r\n posts?: Post[];\r\n loading?: boolean;\r\n className?: string;\r\n}\r\n\r\nexport function BlogSection({\r\n posts: propPosts,\r\n loading: propLoading,\r\n className,\r\n}: BlogSectionProps) {\r\n const { t } = useTranslation(\"blog-section\");\r\n const { posts: hookPosts, loading: hookLoading } = useRecentPosts(3);\r\n\r\n const posts = propPosts ?? hookPosts;\r\n const loading = propLoading ?? hookLoading;\r\n\r\n return (\r\n <section className={cn(\"py-16 md:py-24\", className)}>\r\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-4\">\r\n {/* Header */}\r\n <div className=\"text-center mb-12\">\r\n <Badge variant=\"secondary\" className=\"mb-4\">\r\n {t(\"tagline\", \"Latest Updates\")}\r\n </Badge>\r\n <h2 className=\"text-3xl font-bold md:text-4xl lg:text-5xl mb-4\">\r\n {t(\"title\", \"From Our Blog\")}\r\n </h2>\r\n <p className=\"text-muted-foreground max-w-2xl mx-auto mb-6\">\r\n {t(\r\n \"subtitle\",\r\n \"Discover the latest trends, tips, and insights from our team of experts.\"\r\n )}\r\n </p>\r\n <Button variant=\"link\" asChild>\r\n <Link to=\"/blog\">\r\n {t(\"viewAll\", \"View all articles\")}\r\n <ArrowRight className=\"ml-2 h-4 w-4\" />\r\n </Link>\r\n </Button>\r\n </div>\r\n\r\n {/* Posts Grid */}\r\n <div className=\"grid md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto\">\r\n {loading ? (\r\n [...Array(3)].map((_, i) => (\r\n <Card key={i} className=\"overflow-hidden p-0 animate-pulse\">\r\n <div className=\"aspect-video bg-muted\"></div>\r\n <CardHeader className=\"pt-6 pb-2\">\r\n <div className=\"flex items-center gap-2 mb-2\">\r\n <div className=\"h-5 w-16 bg-muted rounded\"></div>\r\n <div className=\"h-4 w-20 bg-muted rounded\"></div>\r\n </div>\r\n <div className=\"h-6 w-3/4 bg-muted rounded\"></div>\r\n </CardHeader>\r\n <CardContent className=\"py-0\">\r\n <div className=\"h-4 w-full bg-muted rounded mb-2\"></div>\r\n <div className=\"h-4 w-2/3 bg-muted rounded\"></div>\r\n </CardContent>\r\n <CardFooter className=\"pb-2\">\r\n <div className=\"h-4 w-24 bg-muted rounded\"></div>\r\n </CardFooter>\r\n </Card>\r\n ))\r\n ) : (\r\n posts.map((post) => (\r\n <div key={post.id} className=\"contents\" data-db-table=\"posts\" data-db-id={post.id}>\r\n <Card className=\"overflow-hidden group p-0\">\r\n <div className=\"aspect-video overflow-hidden\">\r\n <Link to={`/blog/${post.slug}`}>\r\n <img\r\n src={post.featured_image || \"/images/placeholder.png\"}\r\n alt={post.title}\r\n className=\"w-full h-full object-cover transition-transform duration-300 group-hover:scale-105\"\r\n onError={(e) => {\r\n e.currentTarget.style.display = \"none\";\r\n }}\r\n />\r\n </Link>\r\n </div>\r\n <CardHeader className=\"pt-6 pb-2\">\r\n <div className=\"flex items-center gap-2 mb-2\">\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {post.category_name || post.category}\r\n </Badge>\r\n <span className=\"text-xs text-muted-foreground\">\r\n {new Date(post.published_at).toLocaleDateString()}\r\n </span>\r\n </div>\r\n <Link to={`/blog/${post.slug}`}>\r\n <h3 className=\"text-lg font-semibold hover:text-primary transition-colors line-clamp-2\">\r\n {post.title}\r\n </h3>\r\n </Link>\r\n </CardHeader>\r\n <CardContent className=\"py-0\">\r\n <p className=\"text-sm text-muted-foreground line-clamp-2\">\r\n {post.excerpt}\r\n </p>\r\n </CardContent>\r\n <CardFooter className=\"pb-2\">\r\n <Link\r\n to={`/blog/${post.slug}`}\r\n className=\"text-sm font-medium text-primary hover:underline inline-flex items-center whitespace-nowrap\"\r\n >\r\n {t(\"readMore\", \"Read more\")}\r\n <ArrowRight className=\"ml-1 h-3 w-3 shrink-0\" />\r\n </Link>\r\n </CardFooter>\r\n </Card>\r\n </div>\r\n ))\r\n )}\r\n </div>\r\n </div>\r\n </section>\r\n );\r\n}\r\n"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "blog-section/lang/en.json",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"title": "Cards Carousel Section",
|
|
5
5
|
"description": "Apple-style expandable cards carousel with smooth animations. Cards expand into full-screen modals on click. Features horizontal scrolling, navigation buttons, and responsive design.",
|
|
6
6
|
"dependencies": [
|
|
7
|
-
"
|
|
7
|
+
"motion",
|
|
8
8
|
"lucide-react"
|
|
9
9
|
],
|
|
10
10
|
"registryDependencies": [],
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "cards-carousel-section/cards-carousel-section.tsx",
|
|
21
21
|
"type": "registry:component",
|
|
22
22
|
"target": "$modules$/cards-carousel-section/cards-carousel-section.tsx",
|
|
23
|
-
"content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { motion, AnimatePresence } from \"
|
|
23
|
+
"content": "import { useEffect, useRef, useState, createContext, useContext } from \"react\";\r\nimport type { ReactNode } from \"react\";\r\nimport { motion, AnimatePresence } from \"motion/react\";\r\nimport { X, ChevronLeft, ChevronRight } from \"lucide-react\";\r\nimport { cn } from \"@/lib/utils\";\r\n\r\n// Context for outside click detection\r\nconst CarouselContext = createContext<{\r\n onCardClose: (index: number) => void;\r\n currentIndex: number;\r\n}>({\r\n onCardClose: () => {},\r\n currentIndex: 0,\r\n});\r\n\r\n// Card interface\r\ninterface CardData {\r\n category: string;\r\n title: string;\r\n src: string;\r\n content: ReactNode;\r\n}\r\n\r\n// Card Component\r\nexport function Card({\r\n card,\r\n index,\r\n layout = false,\r\n}: {\r\n card: CardData;\r\n index: number;\r\n layout?: boolean;\r\n}) {\r\n const [open, setOpen] = useState(false);\r\n const containerRef = useRef<HTMLDivElement>(null);\r\n const { onCardClose } = useContext(CarouselContext);\r\n\r\n const handleOpen = () => setOpen(true);\r\n const handleClose = () => {\r\n setOpen(false);\r\n onCardClose(index);\r\n };\r\n\r\n useEffect(() => {\r\n function onKeyDown(event: KeyboardEvent) {\r\n if (event.key === \"Escape\") {\r\n handleClose();\r\n }\r\n }\r\n\r\n if (open) {\r\n document.body.style.overflow = \"hidden\";\r\n window.addEventListener(\"keydown\", onKeyDown);\r\n } else {\r\n document.body.style.overflow = \"auto\";\r\n }\r\n\r\n return () => {\r\n window.removeEventListener(\"keydown\", onKeyDown);\r\n document.body.style.overflow = \"auto\";\r\n };\r\n }, [open, handleClose]);\r\n\r\n return (\r\n <>\r\n <AnimatePresence>\r\n {open && (\r\n <div className=\"fixed inset-0 h-screen z-50 overflow-auto\">\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n className=\"bg-black/80 backdrop-blur-lg h-full w-full fixed inset-0\"\r\n onClick={handleClose}\r\n />\r\n <motion.div\r\n initial={{ opacity: 0 }}\r\n animate={{ opacity: 1 }}\r\n exit={{ opacity: 0 }}\r\n ref={containerRef}\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n className=\"max-w-5xl mx-auto bg-white dark:bg-neutral-900 h-fit z-[60] my-10 p-4 md:p-10 rounded-3xl relative\"\r\n >\r\n <button\r\n className=\"sticky top-4 h-8 w-8 right-0 ml-auto bg-black dark:bg-white rounded-full flex items-center justify-center\"\r\n onClick={handleClose}\r\n >\r\n <X className=\"h-6 w-6 text-neutral-100 dark:text-neutral-900\" />\r\n </button>\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-base font-medium text-black dark:text-white\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-2xl md:text-5xl font-semibold text-neutral-700 mt-4 dark:text-white\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n <div className=\"py-10\">{card.content}</div>\r\n </motion.div>\r\n </div>\r\n )}\r\n </AnimatePresence>\r\n <motion.button\r\n layoutId={layout ? `card-${card.title}` : undefined}\r\n onClick={handleOpen}\r\n className=\"rounded-3xl bg-gray-100 dark:bg-neutral-900 h-80 w-56 md:h-[40rem] md:w-96 overflow-hidden flex flex-col items-start justify-start relative z-10\"\r\n >\r\n <div className=\"absolute h-full top-0 inset-x-0 bg-gradient-to-b from-black/50 via-transparent to-transparent z-30 pointer-events-none\" />\r\n <div className=\"relative z-40 p-8\">\r\n <motion.p\r\n layoutId={layout ? `category-${card.title}` : undefined}\r\n className=\"text-white text-sm md:text-base font-medium text-left\"\r\n >\r\n {card.category}\r\n </motion.p>\r\n <motion.p\r\n layoutId={layout ? `title-${card.title}` : undefined}\r\n className=\"text-white text-xl md:text-3xl font-semibold max-w-xs text-left mt-2\"\r\n >\r\n {card.title}\r\n </motion.p>\r\n </div>\r\n <img\r\n src={card.src}\r\n alt={card.title}\r\n className=\"object-cover absolute z-10 inset-0 w-full h-full\"\r\n />\r\n </motion.button>\r\n </>\r\n );\r\n}\r\n\r\n// Carousel Component\r\nexport function Carousel({ items }: { items: ReactNode[] }) {\r\n const carouselRef = useRef<HTMLDivElement>(null);\r\n const [canScrollLeft, setCanScrollLeft] = useState(false);\r\n const [canScrollRight, setCanScrollRight] = useState(true);\r\n const [currentIndex, setCurrentIndex] = useState(0);\r\n\r\n const checkScrollability = () => {\r\n if (carouselRef.current) {\r\n const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current;\r\n setCanScrollLeft(scrollLeft > 0);\r\n setCanScrollRight(scrollLeft < scrollWidth - clientWidth);\r\n }\r\n };\r\n\r\n useEffect(() => {\r\n checkScrollability();\r\n }, []);\r\n\r\n const scrollLeft = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: -300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const scrollRight = () => {\r\n if (carouselRef.current) {\r\n carouselRef.current.scrollBy({ left: 300, behavior: \"smooth\" });\r\n }\r\n };\r\n\r\n const handleCardClose = (index: number) => {\r\n if (carouselRef.current) {\r\n const cardWidth = isMobile() ? 230 : 384;\r\n const gap = isMobile() ? 4 : 8;\r\n const scrollPosition = (cardWidth + gap) * (index + 1);\r\n carouselRef.current.scrollTo({\r\n left: scrollPosition,\r\n behavior: \"smooth\",\r\n });\r\n setCurrentIndex(index);\r\n }\r\n };\r\n\r\n const isMobile = () => {\r\n return window && window.innerWidth < 768;\r\n };\r\n\r\n return (\r\n <CarouselContext.Provider value={{ onCardClose: handleCardClose, currentIndex }}>\r\n <div className=\"relative w-full\">\r\n <div\r\n className=\"flex w-full overflow-x-scroll overscroll-x-auto py-10 md:py-20 scroll-smooth [scrollbar-width:none]\"\r\n ref={carouselRef}\r\n onScroll={checkScrollability}\r\n >\r\n <div className=\"absolute right-0 z-[1000] h-auto w-[5%] overflow-hidden bg-gradient-to-l from-background to-transparent pointer-events-none\" />\r\n <div className=\"flex flex-row justify-start gap-4 pl-4 max-w-7xl mx-auto\">\r\n {items.map((item, index) => (\r\n <motion.div\r\n initial={{ opacity: 0, y: 20 }}\r\n animate={{\r\n opacity: 1,\r\n y: 0,\r\n transition: {\r\n duration: 0.5,\r\n delay: 0.2 * index,\r\n ease: \"easeOut\",\r\n },\r\n }}\r\n key={\"card\" + index}\r\n className=\"last:pr-[5%] md:last:pr-[33%] rounded-3xl\"\r\n >\r\n {item}\r\n </motion.div>\r\n ))}\r\n </div>\r\n </div>\r\n <div className=\"flex justify-end gap-2 mr-10\">\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollLeft}\r\n disabled={!canScrollLeft}\r\n >\r\n <ChevronLeft className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n <button\r\n className=\"relative z-40 h-10 w-10 rounded-full bg-gray-100 flex items-center justify-center disabled:opacity-50\"\r\n onClick={scrollRight}\r\n disabled={!canScrollRight}\r\n >\r\n <ChevronRight className=\"h-6 w-6 text-gray-500\" />\r\n </button>\r\n </div>\r\n </div>\r\n </CarouselContext.Provider>\r\n );\r\n}\r\n\r\n// Section Component\r\ninterface CardsCarouselSectionProps {\r\n title?: string;\r\n items: CardData[];\r\n className?: string;\r\n}\r\n\r\nexport function CardsCarouselSection({\r\n title,\r\n items,\r\n className,\r\n}: CardsCarouselSectionProps) {\r\n const cards = items.map((card, index) => (\r\n <Card key={card.src} card={card} index={index} />\r\n ));\r\n\r\n return (\r\n <section className={cn(\"w-full py-16 md:py-20\", className)}>\r\n {title && (\r\n <h2 className=\"max-w-7xl pl-4 mx-auto text-xl md:text-5xl font-bold text-neutral-800 dark:text-neutral-200\">\r\n {title}\r\n </h2>\r\n )}\r\n <Carousel items={cards} />\r\n </section>\r\n );\r\n}\r\n"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"path": "cards-carousel-section/lang/en.json",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"path": "cart-drawer/cart-drawer.tsx",
|
|
21
21
|
"type": "registry:component",
|
|
22
22
|
"target": "$modules$/cart-drawer/cart-drawer.tsx",
|
|
23
|
-
"content": "import { Link } from \"react-router\";\nimport { Minus, Plus } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\n\ninterface CartDrawerProps {\n checkoutHref?: string;\n className?: string;\n showTrigger?: boolean;\n}\n\nexport function CartDrawer({\n checkoutHref = \"/checkout\",\n className,\n showTrigger = true,\n}: CartDrawerProps) {\n const { t } = useTranslation(\"cart-drawer\");\n const {\n state,\n removeItem,\n updateQuantity,\n isDrawerOpen,\n setDrawerOpen,\n } = useCart();\n const { items, total } = state;\n const currency = (constants.site as any).currency || \"USD\";\n\n const getProductPrice = (product: {\n price: number;\n sale_price?: number;\n on_sale?: boolean;\n }) => {\n return product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n };\n\n const handleQuantityChange = (id: string | number, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(id);\n } else {\n updateQuantity(id, newQuantity);\n }\n };\n\n return (\n <Sheet open={isDrawerOpen} onOpenChange={setDrawerOpen}
|
|
23
|
+
"content": "import { Link } from \"react-router\";\nimport { Minus, Plus } from \"lucide-react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Sheet,\n SheetContent,\n SheetHeader,\n SheetTitle,\n} from \"@/components/ui/sheet\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCart, formatPrice } from \"@/modules/ecommerce-core\";\nimport constants from \"@/constants/constants.json\";\n\ninterface CartDrawerProps {\n checkoutHref?: string;\n className?: string;\n showTrigger?: boolean;\n}\n\nexport function CartDrawer({\n checkoutHref = \"/checkout\",\n className,\n showTrigger = true,\n}: CartDrawerProps) {\n const { t } = useTranslation(\"cart-drawer\");\n const {\n state,\n removeItem,\n updateQuantity,\n isDrawerOpen,\n setDrawerOpen,\n } = useCart();\n const { items, total } = state;\n const currency = (constants.site as any).currency || \"USD\";\n\n const getProductPrice = (product: {\n price: number;\n sale_price?: number;\n on_sale?: boolean;\n }) => {\n return product.on_sale && product.sale_price\n ? product.sale_price\n : product.price;\n };\n\n const handleQuantityChange = (id: string | number, newQuantity: number) => {\n if (newQuantity <= 0) {\n removeItem(id);\n } else {\n updateQuantity(id, newQuantity);\n }\n };\n\n return (\n <Sheet open={isDrawerOpen} onOpenChange={setDrawerOpen}>\n <SheetContent className=\"w-full sm:max-w-md flex flex-col px-6 pb-8\">\n <SheetHeader>\n <SheetTitle>{t(\"title\", \"Shopping cart\")}</SheetTitle>\n </SheetHeader>\n\n <div className=\"flex-1 overflow-y-auto mt-8\">\n {items.length === 0 ? (\n <p className=\"text-center text-muted-foreground py-8\">\n {t(\"empty\", \"Your cart is empty\")}\n </p>\n ) : (\n <ul className=\"-my-6 divide-y divide-border\">\n {items.map((item) => (\n <li key={item.id} className=\"flex py-6\">\n <div className=\"size-24 shrink-0 overflow-hidden rounded-md border border-border\">\n <img\n alt={item.product.name}\n src={item.product.images[0] || \"/images/placeholder.png\"}\n className=\"size-full object-cover\"\n />\n </div>\n\n <div className=\"ml-4 flex flex-1 flex-col\">\n <div>\n <div className=\"flex justify-between text-base font-medium\">\n <h3>\n <Link\n to={`/products/${item.product.slug}`}\n onClick={() => setDrawerOpen(false)}\n >\n {item.product.name}\n </Link>\n </h3>\n <p className=\"ml-4\">\n {formatPrice(getProductPrice(item.product), currency)}\n </p>\n </div>\n {item.product.category_name && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {item.product.category_name}\n </p>\n )}\n </div>\n <div className=\"flex flex-1 items-end justify-between text-sm\">\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-6 w-6\"\n onClick={() =>\n handleQuantityChange(item.id, item.quantity - 1)\n }\n >\n <Minus className=\"h-3 w-3\" />\n </Button>\n <span className=\"w-8 text-center text-sm\">\n {item.quantity}\n </span>\n <Button\n variant=\"outline\"\n size=\"icon\"\n className=\"h-6 w-6\"\n onClick={() =>\n handleQuantityChange(item.id, item.quantity + 1)\n }\n >\n <Plus className=\"h-3 w-3\" />\n </Button>\n </div>\n\n <button\n type=\"button\"\n onClick={() => removeItem(item.id)}\n className=\"font-medium text-primary hover:text-primary/80\"\n >\n {t(\"remove\", \"Remove\")}\n </button>\n </div>\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n\n <div className=\"border-t border-border pt-6 mt-6\">\n <div className=\"flex justify-between text-base font-medium\">\n <p>{t(\"subtotal\", \"Subtotal\")}</p>\n <p>{formatPrice(total, currency)}</p>\n </div>\n <p className=\"mt-0.5 text-sm text-muted-foreground\">\n {t(\"shippingNote\", \"Shipping and taxes calculated at checkout.\")}\n </p>\n <div className=\"mt-6\">\n <Button asChild className=\"w-full\" disabled={items.length === 0}>\n <Link to={checkoutHref} onClick={() => setDrawerOpen(false)}>\n {t(\"checkout\", \"Checkout\")}\n </Link>\n </Button>\n </div>\n </div>\n </SheetContent>\n </Sheet>\n );\n}\n"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"path": "cart-drawer/lang/en.json",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"path": "category-section/category-section.tsx",
|
|
17
17
|
"type": "registry:component",
|
|
18
18
|
"target": "$modules$/category-section/category-section.tsx",
|
|
19
|
-
"content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCategories } from \"@/modules/ecommerce-core\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories?: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories: propCategories,\n loading: propLoading,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n const { categories: hookCategories, loading: hookLoading } = useCategories();\n\n const categories = propCategories ?? hookCategories;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <
|
|
19
|
+
"content": "import { Link } from \"react-router\";\nimport { ArrowRight } from \"lucide-react\";\nimport { Card } from \"@/components/ui/card\";\nimport { useTranslation } from \"react-i18next\";\nimport { useCategories } from \"@/modules/ecommerce-core\";\n\nexport interface CategoryItem {\n id: string | number;\n slug: string;\n name: string;\n description?: string;\n image: string;\n}\n\nexport interface CategorySectionProps {\n categories?: CategoryItem[];\n loading?: boolean;\n}\n\nexport function CategorySection({\n categories: propCategories,\n loading: propLoading,\n}: CategorySectionProps) {\n const { t } = useTranslation(\"category-section\");\n const { categories: hookCategories, loading: hookLoading } = useCategories();\n\n const categories = propCategories ?? hookCategories;\n const loading = propLoading ?? hookLoading;\n\n return (\n <section className=\"py-8 sm:py-12 md:py-16 lg:py-20 bg-gradient-to-b from-background to-muted/20 border-t border-border/20\">\n <div className=\"w-full max-w-[var(--container-max-width)] mx-auto px-3 sm:px-4 lg:px-8\">\n <div className=\"text-center mb-6 sm:mb-8 md:mb-12 lg:mb-16 px-2\">\n <h2 className=\"text-xl sm:text-2xl md:text-3xl lg:text-4xl xl:text-5xl font-bold mb-2 sm:mb-3 md:mb-4 bg-gradient-to-r from-primary to-primary/80 bg-clip-text text-transparent leading-normal pb-1\">\n {t(\"title\", \"Shop by Category\")}\n </h2>\n <div className=\"w-12 sm:w-16 h-1 bg-gradient-to-r from-primary/50 to-primary/20 mx-auto mb-3 sm:mb-4 md:mb-6 rounded-full\"></div>\n <p className=\"text-xs sm:text-sm md:text-base lg:text-lg text-muted-foreground max-w-3xl mx-auto leading-relaxed\">\n {t(\"subtitle\", \"Discover our carefully curated collections\")}\n </p>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4 sm:gap-6 lg:gap-8\">\n {loading\n ? [...Array(4)].map((_, i) => (\n <div key={i} className=\"animate-pulse\">\n <div className=\"aspect-[3/2] bg-muted rounded-xl mb-4\"></div>\n <div className=\"h-5 bg-muted rounded w-3/4 mb-2\"></div>\n <div className=\"h-4 bg-muted rounded w-1/2\"></div>\n </div>\n ))\n : categories.map((category) => (\n <div key={category.id} className=\"contents\" data-db-table=\"product_categories\" data-db-id={category.id}>\n <Link\n to={`/products?category=${category.slug}`}\n className=\"group block\"\n >\n <Card className=\"overflow-hidden border-0 p-0 shadow-lg hover:shadow-2xl transition-all duration-500 group-hover:-translate-y-2 rounded-2xl\">\n <div className=\"aspect-[4/3] relative overflow-hidden\">\n <img\n src={category.image}\n alt={category.name}\n className=\"absolute inset-0 w-full h-full object-cover group-hover:scale-110 transition-transform duration-700\"\n />\n <div className=\"absolute inset-0 bg-gradient-to-t from-black/60 via-black/20 to-transparent group-hover:from-black/70 transition-all duration-300\"></div>\n\n <div className=\"absolute bottom-0 left-0 right-0 p-4 sm:p-6\">\n <h3 className=\"text-lg sm:text-xl font-bold text-white mb-1 sm:mb-2 group-hover:text-primary-foreground transition-colors\">\n {category.name}\n </h3>\n {category.description && (\n <p className=\"text-xs sm:text-sm text-white/90 line-clamp-2 group-hover:text-white transition-colors\">\n {category.description}\n </p>\n )}\n\n <div className=\"flex items-center mt-2 sm:mt-3 text-white/80 group-hover:text-white transition-all duration-300 transform group-hover:translate-x-1\">\n <span className=\"text-xs sm:text-sm font-medium mr-2\">\n {t(\"explore\", \"Explore\")}\n </span>\n <ArrowRight className=\"w-3 h-3 sm:w-4 sm:h-4\" />\n </div>\n </div>\n </div>\n </Card>\n </Link>\n </div>\n ))}\n </div>\n </div>\n </section>\n );\n}\n"
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"path": "category-section/lang/en.json",
|