diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 0000000..cc12130 --- /dev/null +++ b/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selecter that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 0000000..c1525b8 Binary files /dev/null and b/coverage/favicon.png differ diff --git a/coverage/functions/index.html b/coverage/functions/index.html new file mode 100644 index 0000000..3db72d6 --- /dev/null +++ b/coverage/functions/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for functions + + + + + + + + + +
+
+

All files functions

+
+ +
+ 0% + Statements + 0/19 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
index.js +
+
0%0/190%0/10%0/10%0/19
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/functions/index.js.html b/coverage/functions/index.js.html new file mode 100644 index 0000000..350501b --- /dev/null +++ b/coverage/functions/index.js.html @@ -0,0 +1,142 @@ + + + + + + Code coverage report for functions/index.js + + + + + + + + + +
+
+

All files / functions index.js

+
+ +
+ 0% + Statements + 0/19 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
/**
+ * Import function triggers from their respective submodules:
+ *
+ * const {onCall} = require("firebase-functions/v2/https");
+ * const {onDocumentWritten} = require("firebase-functions/v2/firestore");
+ *
+ * See a full list of supported triggers at https://firebase.google.com/docs/functions
+ */
+
+const {onRequest} = require("firebase-functions/v2/https");
+const logger = require("firebase-functions/logger");
+
+// Create and deploy your first functions
+// https://firebase.google.com/docs/functions/get-started
+
+// exports.helloWorld = onRequest((request, response) => {
+//   logger.info("Hello logs!", {structuredData: true});
+//   response.send("Hello from Firebase!");
+// });
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/index.html b/coverage/index.html new file mode 100644 index 0000000..4f33d0f --- /dev/null +++ b/coverage/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 6.16% + Statements + 102/1655 +
+ + +
+ 19.51% + Branches + 8/41 +
+ + +
+ 11.42% + Functions + 4/35 +
+ + +
+ 6.16% + Lines + 102/1655 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
functions +
+
0%0/190%0/10%0/10%0/19
src +
+
0%0/640%0/30%0/30%0/64
src/components +
+
4.62%48/103726.31%5/1916.66%3/184.62%48/1037
src/components/pages +
+
17.58%54/30720%3/1510%1/1017.58%54/307
src/utilities +
+
0%0/2280%0/30%0/30%0/228
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000..6ed6831 Binary files /dev/null and b/coverage/sort-arrow-sprite.png differ diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 0000000..2bb296a --- /dev/null +++ b/coverage/sorter.js @@ -0,0 +1,196 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if ( + row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()) + ) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/coverage/src/App.jsx.html b/coverage/src/App.jsx.html new file mode 100644 index 0000000..58bca35 --- /dev/null +++ b/coverage/src/App.jsx.html @@ -0,0 +1,247 @@ + + + + + + Code coverage report for src/App.jsx + + + + + + + + + +
+
+

All files / src App.jsx

+
+ +
+ 0% + Statements + 0/41 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/41 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useAuthState } from './utilities/firebase';
+import { HashRouter as Router, Routes, Route } from 'react-router-dom';
+import 'bootstrap-icons/font/bootstrap-icons.css';
+import 'bootstrap/dist/css/bootstrap.min.css';
+ 
+import SignInPage from './components/pages/SignIn.jsx';
+import './App.css';
+ 
+import Navigationbar from './components/Navigation';
+import HomePage from './components/pages/HomePage';
+import RequestsPage from './components/pages/RequestsPage';
+import ProfilePage from './components/pages/ProfilePage';
+import RequestFormPage from './components/pages/RequestForm'; 
+import Header from './components/Header';
+ 
+const App = () => {
+  const [user, loading, error] = useAuthState(); 
+ 
+  if (loading) {
+    return <div>Loading...</div>; 
+  }
+ 
+  if (error) {
+    return <div>Error: {error.message}</div>; // Handle error if authentication fails
+  }
+ 
+  return (
+    <Router>
+      <div className="App">
+        {/* If user is authenticated, show Header, Navbar, and Routes, else show SignInPage */}
+        {!user ? (
+          <SignInPage />  // Show sign-in page if not authenticated
+        ) : (
+          <>
+            <Header />
+ 
+            <div className="content flex-grow"> 
+              <Routes>
+                <Route path="/" element={<HomePage />} />
+                <Route path="/requests" element={<RequestsPage />} />
+                <Route path="/profile" element={<ProfilePage />} />
+                <Route path="/requestform" element={<RequestFormPage />} />
+              </Routes>
+            </div>
+ 
+            <Navigationbar />
+          </>
+        )}
+      </div>
+    </Router>
+  );
+};
+ 
+export default App;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/Home.jsx.html b/coverage/src/Home.jsx.html new file mode 100644 index 0000000..43d0bd9 --- /dev/null +++ b/coverage/src/Home.jsx.html @@ -0,0 +1,130 @@ + + + + + + Code coverage report for src/Home.jsx + + + + + + + + + +
+
+

All files / src Home.jsx

+
+ +
+ 0% + Statements + 0/13 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import "./SignIn.css"
+ 
+const HomePage = () => {
+    return (
+        <div className="sign-in-page">
+            <div className="sign-in">
+                <div className="house">
+                    <i class="bi bi-house-door"></i>
+                </div>
+                <p className="title">Welcome</p>
+                
+            </div>
+        </div>
+    );
+}
+export default HomePage;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Buttons.jsx.html b/coverage/src/components/Buttons.jsx.html new file mode 100644 index 0000000..98f0d6a --- /dev/null +++ b/coverage/src/components/Buttons.jsx.html @@ -0,0 +1,220 @@ + + + + + + Code coverage report for src/components/Buttons.jsx + + + + + + + + + +
+
+

All files / src/components Buttons.jsx

+
+ +
+ 42.85% + Statements + 15/35 +
+ + +
+ 66.66% + Branches + 2/3 +
+ + +
+ 50% + Functions + 2/4 +
+ + +
+ 42.85% + Lines + 15/35 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +461x +1x +1x +1x +  +1x +1x +1x +1x +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +1x +1x +  +1x
import React, { useState } from 'react';
+import { Button, DropdownButton } from 'react-bootstrap';
+import { signInWithGoogle, signOut, useAuthState } from '../utilities/firebase';
+import "./Buttons.css"
+ 
+  export const GreenButton = ({onClick, text}) => (
+    <Button className="green-custom-button" 
+    onClick={onClick}
+    >{text}</Button> 
+  );
+ 
+  export const GreyButton = ({onClick, text}) => (
+    <Button className="grey-custom-button" onClick={onClick}>{text}</Button> 
+  );
+  
+  export const GreyOutlineButton = ({ onClick, text, disabled }) => {
+    const [isPressed, setIsPressed] = useState(false);
+ 
+    const handleClick = (e) => {
+      if (!disabled) {
+        setIsPressed(!isPressed); // Toggle the pressed state
+        if (onClick) {
+          onClick(e); // Trigger any additional onClick logic passed via props
+        }
+      }
+    };
+ 
+    return (
+      <Button
+        className={`grey-outline-custom-button ${isPressed ? 'pressed' : ''}`}
+        variant="outline-secondary"
+        onClick={handleClick}
+        disabled={disabled}
+        size="sm"
+      >
+        {text}
+      </Button>
+    );
+  };
+ 
+  const AuthButton = () => {
+    const [user] = useAuthState();
+    return user ? <GreenButton onClick={signOut} text={'Sign out'}/> : <GreenButton onClick={signInWithGoogle} text={'Sign in'} />
+  };
+ 
+  export default AuthButton;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/CountdownTimer.jsx.html b/coverage/src/components/CountdownTimer.jsx.html new file mode 100644 index 0000000..caa9281 --- /dev/null +++ b/coverage/src/components/CountdownTimer.jsx.html @@ -0,0 +1,244 @@ + + + + + + Code coverage report for src/components/CountdownTimer.jsx + + + + + + + + + +
+
+

All files / src/components CountdownTimer.jsx

+
+ +
+ 0% + Statements + 0/43 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/43 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useEffect, useState } from 'react';
+import { useDbUpdate ,useAuthState} from "../utilities/firebase";
+ 
+export const calcTime = (timer) => {
+    const day = Math.floor(timer / (60*24));
+    const hour = Math.floor(timer / 60);
+    const min = Math.floor(timer);
+    if (day > 0) {
+        return (
+            `${day} day(s)`
+        );
+    }
+    else {
+        if (hour > 0) {
+            return(
+                `${hour} hour(s)`
+            );
+        }
+        else{
+            return(`${min} minute(s)`)
+        }
+    }
+}
+ 
+const CountdownTimer = ({request}) => {
+    
+  const postTime = new Date(request.post_time).valueOf();
+  const [timeRemaining, setTimeRemaining] = useState((postTime/60000 + request.timer) - (new Date()/60000));
+  const [updateData, result] = useDbUpdate(`/requests/${request.request_id}`);
+ 
+  useEffect(() => {
+    const timerInterval = setInterval(() => {
+        setTimeRemaining((postTime/60000 + request.timer) - (new Date()/60000));
+        if(timeRemaining <= 0){
+            const updatedData = { ...(request), request_status: "Closed"};
+            try{
+                updateData(updatedData)
+            } 
+            catch (error) {
+                console.error("Error occurred:", error.message);
+            }
+        }
+        
+    }, (1000));
+ 
+    // Cleanup the interval when the component unmounts
+    return () => clearInterval(timerInterval);
+    },[]);
+ 
+    return(
+        <div>{calcTime(timeRemaining)} remaining</div>
+    );
+}
+export default CountdownTimer;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/EditProfileModal.jsx.html b/coverage/src/components/EditProfileModal.jsx.html new file mode 100644 index 0000000..728e7a3 --- /dev/null +++ b/coverage/src/components/EditProfileModal.jsx.html @@ -0,0 +1,493 @@ + + + + + + Code coverage report for src/components/EditProfileModal.jsx + + + + + + + + + +
+
+

All files / src/components EditProfileModal.jsx

+
+ +
+ 5.71% + Statements + 6/105 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 5.71% + Lines + 6/105 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +1371x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
// EditProfile.jsx
+import React from 'react'
+import { useState, useEffect } from 'react';
+import './EditProfileModal.css'
+import { useDbData, useDbUpdate } from '../utilities/firebase';
+ 
+ 
+function EditProfileModal({ closeModal, userId }) {
+    const [userData, userDataError] = useDbData(`/users/${userId}`);
+ 
+    const [address, setAddress] = useState('');
+    const [apartment, setApartment] = useState('');
+    const [city, setCity] = useState('');
+    const [state, setStateLoc] = useState('');
+    const [zip, setZip] = useState('');
+    const [neighborhoodCode, setNeighborhoodCode] = useState('');
+ 
+    const [updateData, result] = useDbUpdate(`/users/${userId}`);
+ 
+ 
+    useEffect(() => {
+        if (userData) {
+            setAddress(userData.Address || ''); 
+            setApartment(userData.Apartment || ''); 
+            setCity(userData.City || '');
+            setStateLoc(userData.StateLoc || '');
+            setZip(userData.Zip || '');
+            setNeighborhoodCode(userData.NeighborhoodCode || '');
+        }
+    }, [userData]);
+ 
+    const handleSave = async () => {
+        const updates = {
+            Address: address,
+            Apartment: apartment,
+            City: city,
+            StateLoc: state,
+            Zip: zip,
+            NeighborhoodCode: neighborhoodCode
+        };
+ 
+        try {
+            await updateData(updates);
+            console.log("Profile updated successfully!");
+ 
+            closeModal();
+        } catch (error) {
+            console.error("Error updating profile:", error);
+        }
+    };
+ 
+ 
+ 
+    if (!userData) {
+        return <p>Loading...</p>; 
+    }
+ 
+    return (
+        <div className='modalBackground'>
+            <div className="modalContainer">
+                {/* <div className="titleCloseBtn">
+                    <button onClick={() => closeModal(false)}> X </button>
+                </div> */}
+                <div className="title">
+                    <h2>Edit Profile</h2>
+                </div>
+                <div className="body">
+ 
+ 
+                    <div className="address">
+                        <h3>Home Address</h3>
+                        <input
+                            className="input fullWidth"
+                            value={address}
+                            onChange={(e) => setAddress(e.target.value)}
+                            placeholder="Enter your address"
+                        />
+                        <input
+                            className="input fullWidth"
+                            value={apartment}
+                            onChange={(e) => setApartment(e.target.value)}
+                            placeholder="Apartment, suite, etc."
+                        />
+                        <input
+                            className="input fullWidth"
+                            value={city}
+                            onChange={(e) => setCity(e.target.value)}
+                            placeholder="City"
+                        />
+ 
+                        {/* <input className="input fullWidth" value="Address" />
+                        <input className="input fullWidth" value="Apartment, suite, etc" /> */}
+                        {/* <input className="input fullWidth" value="City" /> */}
+                        <div className="twoColumns">
+                            {/* <input className="input " value="State" />
+                            <input className="input " value="Zip Code" /> */}
+                            <input
+                                className="input"
+                                value={state}
+                                onChange={(e) => setStateLoc(e.target.value)}
+                                placeholder="State"
+                            />
+                            <input
+                                className="input"
+                                value={zip}
+                                onChange={(e) => setZip(e.target.value)}
+                                placeholder="Zip Code"
+                            />
+                        </div>
+                    </div>
+ 
+ 
+                    <div className="neighborhood">
+                        <h3> Neighborhood Code</h3>
+                        {/* <input className="input" value="Enter Here"/> */}
+                        <input
+                            className="input"
+                            value={neighborhoodCode}
+                            onChange={(e) => setNeighborhoodCode(e.target.value)}
+                            placeholder="Enter Neighborhood Code"
+                        />
+                        <p className="smallText">please allow up to 2 days for approval from your Neighborhood Admin</p>
+                    </div>
+ 
+                </div>
+                <div className="footer">
+                    <button  id='cancelBtn' onClick={() => closeModal(false)}>Cancel</button>
+                    <button id='saveBtn' onClick={handleSave} >Save Profile</button>
+                </div>
+ 
+ 
+            </div>
+        </div>
+    )
+}
+ 
+export default EditProfileModal
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Form.jsx.html b/coverage/src/components/Form.jsx.html new file mode 100644 index 0000000..b6137af --- /dev/null +++ b/coverage/src/components/Form.jsx.html @@ -0,0 +1,946 @@ + + + + + + Code coverage report for src/components/Form.jsx + + + + + + + + + +
+
+

All files / src/components Form.jsx

+
+ +
+ 0% + Statements + 0/244 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/244 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
// defining various forms
+import React, { useState, useEffect } from 'react';
+import {Form, Container, Row, Col, Dropdown, DropdownButton, Alert } from 'react-bootstrap';
+import { useDbUpdate ,useAuthState} from "../utilities/firebase";
+import { useNavigate } from 'react-router-dom';
+ 
+import { GreyButton, GreenButton, GreyOutlineButton } from "./Buttons";
+import './Form.css'
+ 
+export const TextOnlyForm = ({text, setText, placeholder}) => (
+        <Form.Control
+            as="textarea"
+            rows={4}
+            value={text}
+            onChange={(e) => setText(e.target.value)}  
+            className='text-only'
+            placeholder={placeholder}
+            style={{ width: '90%', height: 'auto' }}
+        />
+);
+ 
+export const AcceptanceForm = ({request, handleClose}) => {
+    const [updateData, result] = useDbUpdate(`/requests/${request.request_id}`);
+    const [user] = useAuthState();
+    const currentUserID = user?.uid;
+    const [phoneNumber, setPhoneNumber] = useState('');
+    const [msg, setMsg] = useState('');
+    const [deliveryOption, setDeliveryOption] = useState(''); // State to track the selected option
+    const [err, setErr] = useState(false);
+ 
+    const updatedData = { ...(request), 
+                        request_status: "Pending",
+                        accept_userid : currentUserID,
+                        delivery_pref: [...request.delivery_pref, deliveryOption], // adding selected pref to the end, any repeat indicates a selected option
+                        accept_phone_number: phoneNumber,
+                        accept_msg: msg
+                     };
+    const AcceptRequest = async(evt) => {
+        evt.preventDefault();
+        if(deliveryOption !== ''){
+            try{
+                await(updateData(updatedData));
+                handleClose();
+            } 
+            catch (error) {
+                console.error("Error occurred:", error.message);
+            }
+        }
+        else {
+            setErr(true);
+        }
+        return;
+    }
+ 
+    const handleButtonClick = (option) => {
+        setErr(false);
+        setDeliveryOption(deliveryOption === option ? '' : option);
+    };
+ 
+    return (
+ 
+        <Container className="mt-5">
+            <Form className="custom-form" style={{ background: '#D1E7DD' }}>
+                {/* Phone Number Input */}
+                <Form.Group className="mb-3" controlId="formPhoneNumber">
+                    <Form.Label>Phone Number</Form.Label>
+                    <Form.Control
+                        className="w-100"
+                        type="phone number"
+                        placeholder="Phone number"
+                        onChange={(e) => setPhoneNumber(e.target.value)} 
+                        autoFocus
+                    />
+                </Form.Group>
+                {/* Message Input */}
+                <Form.Group
+                    className="mb-3"
+                    controlId="formMessage"
+                >
+                    <Form.Label>Message</Form.Label>
+                    <Form.Control 
+                        className="w-100"
+                        as="textarea" 
+                        rows={5} 
+                        style={{ minHeight: '100px' }}
+                        placeholder="Write your message"
+                        onChange={(e) => setMsg(e.target.value)}
+                    />
+                </Form.Group>
+                <Form.Group
+                    className="mb-3"
+                    controlId="formOptions"
+                >
+                    <Form.Label>Deliver option (choose one): </Form.Label>
+                    <div className="d-flex flex-wrap">
+                        {(request.delivery_pref).map( (option) => {
+                            return(
+                                <div className="me-2 mb-2">
+                                    <GreyOutlineButton 
+                                        onClick={() => handleButtonClick(option)} 
+                                        text={option}
+                                        disabled={deliveryOption !== '' && deliveryOption !== option}/>
+                                </div>
+                            );
+                        })}
+                    </div>
+                </Form.Group>
+ 
+                {err && (
+                        <Alert variant="danger" className="mb-2 small-alert">
+                        Please select a delivery option. 
+                        </Alert>
+                )}
+                {/* Accept Button */}
+                <div className="d-flex justify-content-center">
+                    <GreyButton onClick={AcceptRequest} text={'Accept Request'}/>
+                </div>
+            </Form>
+        </Container>
+    );
+ 
+}
+ 
+const TimeSelector = ({setTimer}) => {
+    const [selectedDay, setSelectedDay] = useState('');
+    const [selectedHour, setSelectedHour] = useState('');
+    const [selectedMinute, setSelectedMinute] = useState('');
+ 
+    const days = Array.from({ length: 8 }, (_, i) => i); // Generates array [1, 2, 3, ..., 7]
+    const hours = Array.from({ length: 24 }, (_, i) => i);  // Generates array [0, 1, 2, ..., 23]
+    const minutes = Array.from({ length: 60 }, (_, i) => i);  // Generates array [0, 1, 2, ..., 59]
+ 
+    const dropdownMenuStyle = {
+        maxHeight: '160px', // Approx. 5 rows (adjust as needed for row height)
+        overflowY: 'auto',
+    };
+ 
+    return (
+        <Container className="mt-4">
+            <Row className="justify-content-center">
+                <Col xs="auto">
+                    <DropdownButton 
+                        variant="secondary"
+                        id="dropdown-day"
+                        size="sm"
+                        title={selectedDay !== '' ? `${selectedDay} Days` : 'Day'}
+                        onSelect={(e) => {setSelectedDay(e); setTimer(e*24*60+selectedHour*60+selectedMinute*1)}}
+                    >
+                        <div style={dropdownMenuStyle}>
+                            {days.map((day) => (
+                                <Dropdown.Item key={day} eventKey={day}>
+                                    Day {day}
+                                </Dropdown.Item>
+                            ))}
+                        </div>
+                    </DropdownButton>
+                </Col>
+ 
+                <Col xs="auto">
+                    <DropdownButton
+                        variant="secondary"
+                        id="dropdown-hour"
+                        size="sm"
+                        title={selectedHour !== '' ? `${selectedHour} Hours` : 'Hour'}
+                        onSelect={(e) => {setSelectedHour(e); setTimer(selectedDay*24*60+e*60+selectedMinute*1)}}
+                    >
+                        <div style={dropdownMenuStyle}>
+                            {hours.map((hour) => (
+                                <Dropdown.Item key={hour} eventKey={hour}>
+                                    {hour < 10 ? `0${hour}` : hour} Hour(s)
+                                </Dropdown.Item>
+                            ))}
+                        </div>
+                    </DropdownButton>
+                </Col>
+ 
+                <Col xs="auto">
+                    <DropdownButton
+                        variant="secondary"
+                        id="dropdown-minute"
+                        size="sm"
+                        title={selectedMinute !== '' ? `${selectedMinute} Minutes` : 'Min'}
+                        onSelect={(e) => {setSelectedMinute(e); setTimer(selectedDay*24*60+selectedHour*60+e*1)}}
+                    >
+                        <div style={dropdownMenuStyle}>
+                            {minutes.map((minute) => (
+                                <Dropdown.Item key={minute} eventKey={minute}>
+                                    {minute < 10 ? `0${minute}` : minute} Minute(s)
+                                </Dropdown.Item>
+                            ))}
+                        </div>
+                    </DropdownButton>
+                </Col>
+            </Row>
+        </Container>
+    );
+};
+ 
+const MultiSelect = ({deliveryPref, setDeliveryPref, meetUp, setMeetUpLocation}) => {
+    const options = ['Pick up', 'Drop off', 'Meet up'];
+    // const [meetUpLocation, setMeetUpLocation] = useState(''); // Changed variable name
+ 
+    // Handle select logic
+    const handleSelect = (option) => {
+        setDeliveryPref((prevSelected) => {
+            if (prevSelected.includes(option)) {
+                return prevSelected.filter((item) => item !== option); // Deselect the option
+            } else {
+                return [...prevSelected, option]; // Select the option
+            }
+        });
+    };
+ 
+    return (
+        <div>
+            <Form.Label>Select Options:</Form.Label>
+            {options.map((option) => (
+                <Form.Check
+                    key={option}
+                    type="checkbox"
+                    label={option}
+                    checked={deliveryPref.includes(option)}
+                    onChange={() => handleSelect(option)} // Handle checkbox change
+                />
+            ))}
+            {deliveryPref.includes('Meet up') && (
+                <Form.Group className="mt-3">
+                    <Form.Label>Meet-up Location:</Form.Label>
+                    <Form.Control
+                        type="text"
+                        placeholder="Enter location"
+                        value={meetUp} // Updated to use meetUpLocation
+                        onChange={(e) => setMeetUpLocation(e.target.value)} // Update location state
+                    />
+                </Form.Group>
+            )}
+            <div className="mt-3">
+                <strong>Selected: </strong>{deliveryPref.length === 0 ? 'None' : deliveryPref.join(', ')}
+            </div>
+        </div>
+    );
+};
+  
+export const RequestForm= ({data, setDescription, setTimer, deliveryPref, setDeliveryPref, setMeetUpLocation, onClick}) => {
+ 
+    const navigate = useNavigate();
+    const DirectToHome = () => {
+        navigate('/'); 
+      };
+    return (
+ 
+        <Container className="mt-1">
+            <Form className="custom-form">
+                {/* Description Input */}
+                <Form.Group className="mb-3" controlId="formDescription">
+                    <Form.Label>Description</Form.Label>
+                    <Form.Control 
+                        className="w-100"
+                        as="textarea" 
+                        rows={5} 
+                        style={{ minHeight: '100px' }}
+                        placeholder="How can your neighbors help?"
+                        value={data.description}
+                        onChange={(e) => setDescription(e.target.value)}
+                    />
+                </Form.Group>
+                <hr />
+                <Form.Group className="mb-3 d-flex align-items-center" controlId="formExpectedDuration">
+                    <Form.Label>Post Expiration</Form.Label>
+                    <TimeSelector setTimer={setTimer}/>
+                </Form.Group>
+                <hr />
+                <Form.Group controlId="formDelivery">
+                    <Form.Label>Delivery Preference</Form.Label>
+                    <MultiSelect deliveryPref={deliveryPref} setDeliveryPref={setDeliveryPref} meetUp={data.meet_up_loc} setMeetUpLocation={setMeetUpLocation}/>
+                </Form.Group>
+ 
+                {/* Accept/Submit Button */}
+                <div className="d-flex justify-content-center gap-3">
+                    <GreenButton onClick={DirectToHome} text={'Cancel'}/>
+                    <GreenButton onClick={onClick} text={'Submit'}/>
+                </div>
+            </Form>
+        </Container>
+    );
+}
+ 
+export default TextOnlyForm
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Header.jsx.html b/coverage/src/components/Header.jsx.html new file mode 100644 index 0000000..88c0705 --- /dev/null +++ b/coverage/src/components/Header.jsx.html @@ -0,0 +1,142 @@ + + + + + + Code coverage report for src/components/Header.jsx + + + + + + + + + +
+
+

All files / src/components Header.jsx

+
+ +
+ 0% + Statements + 0/16 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+import { Container, Navbar } from 'react-bootstrap';
+import 'bootstrap-icons/font/bootstrap-icons.css';
+import './Header.css';
+ 
+const Header = () => {
+  return (
+    <Navbar bg="light" className="header-navbar fixed-top justify-content-center">
+      <Container className="d-flex justify-content-center">
+        <Navbar.Brand href="/" className="text-center d-flex align-items-center">
+          <i className="bi bi-house-door-fill" style={{ fontSize: '2rem', color: '#4B7260' }}></i>
+          <span className="ms-3 good-neighbor-text">Good Neighbor</span>
+        </Navbar.Brand>
+      </Container>
+    </Navbar>
+  );
+};
+ 
+export default Header;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Modal.jsx.html b/coverage/src/components/Modal.jsx.html new file mode 100644 index 0000000..e269637 --- /dev/null +++ b/coverage/src/components/Modal.jsx.html @@ -0,0 +1,139 @@ + + + + + + Code coverage report for src/components/Modal.jsx + + + + + + + + + +
+
+

All files / src/components Modal.jsx

+
+ +
+ 0% + Statements + 0/16 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { Modal } from 'react-bootstrap';
+import { AcceptanceForm } from "./Form";
+import { calcTime } from './CountdownTimer';
+ 
+const AcceptRequestModal = ({show, handleClose, currentRequest}) => {
+    return (
+        <Modal show={show} onHide={handleClose}>
+            <Modal.Header style={{ background: '#EEEEEE' }} closeButton>
+                <Modal.Title>{currentRequest.username} ({calcTime(currentRequest.timer)} remaining)</Modal.Title>
+            </Modal.Header>
+            <Modal.Body >
+                <p>{currentRequest.description}</p>
+                <AcceptanceForm request={currentRequest} handleClose={handleClose} />
+            </Modal.Body>
+        </Modal>
+    )
+};
+ 
+export default AcceptRequestModal
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Navigation.jsx.html b/coverage/src/components/Navigation.jsx.html new file mode 100644 index 0000000..2a6259d --- /dev/null +++ b/coverage/src/components/Navigation.jsx.html @@ -0,0 +1,181 @@ + + + + + + Code coverage report for src/components/Navigation.jsx + + + + + + + + + +
+
+

All files / src/components Navigation.jsx

+
+ +
+ 0% + Statements + 0/27 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { NavLink } from 'react-router-dom';
+import './Navigation.css'
+import Container from 'react-bootstrap/Container';
+import Nav from 'react-bootstrap/Nav';
+import Navbar from 'react-bootstrap/Navbar';
+ 
+const Navigationbar = () => {
+  return (
+    <Navbar fixed="bottom" className="bg-light border-top">
+      <Container className="d-flex justify-content-around">
+        <Nav className="w-100 d-flex justify-content-between">
+          <Nav.Link as={NavLink} to="/" className="nav-icon" activeClassName="active">
+            <i className="bi bi-house-door-fill"></i>
+            <p>Home</p>
+          </Nav.Link>
+ 
+          <Nav.Link as={NavLink} to="/profile" className="nav-icon" activeClassName="active">
+            <i className="bi bi-person-fill"></i>
+            <p>Profile</p>
+          </Nav.Link>
+ 
+          <Nav.Link as={NavLink} to="/requests" className="nav-icon" activeClassName="active">
+            <i className="bi bi-list-task"></i>
+            <p>Requests</p>
+          </Nav.Link>
+        </Nav>
+      </Container>
+    </Navbar>
+  );
+};
+ 
+export default Navigationbar;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/ProfileModal.jsx.html b/coverage/src/components/ProfileModal.jsx.html new file mode 100644 index 0000000..8bd1a13 --- /dev/null +++ b/coverage/src/components/ProfileModal.jsx.html @@ -0,0 +1,169 @@ + + + + + + Code coverage report for src/components/ProfileModal.jsx + + + + + + + + + +
+
+

All files / src/components ProfileModal.jsx

+
+ +
+ 0% + Statements + 0/25 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+import { Modal, Button } from 'react-bootstrap';
+import { renderStars } from "./renderStars";
+ 
+const ProfileModal = ({ show, handleClose, user, msg, contactNumber }) => {
+    if (!user) return null; // If no user data is passed, return null
+    return (
+        <Modal show={show} onHide={handleClose}>
+            <Modal.Header style={{ background: '#EEEEEE' }} closeButton>
+                <Modal.Title>{user.username}'s Profile</Modal.Title>
+            </Modal.Header>
+            <Modal.Body>
+                <p><strong>Name:</strong> {user.username || 'Not available'}</p>
+                <p><strong>Location:</strong> {user.Address}, {user.Apartment}, {user.City}, {user.StateLoc} {user.Zip}</p>
+                <p><strong>Rating:</strong> {renderStars(user.rate_score)}</p>
+                <p><strong>Tasks Completed:</strong> {user.task_CBU || 0}</p>
+                <p><strong>Tasks Offered:</strong> {user.task_CFU || 0}</p>
+                <p><strong>Contact Number:</strong> {contactNumber || 0}</p>
+                <p><strong>Message:</strong> {msg || 0}</p>
+            </Modal.Body>
+            <Modal.Footer>
+                <Button variant="secondary" onClick={handleClose}>Close</Button>
+            </Modal.Footer>
+        </Modal>
+    );
+};
+ 
+export default ProfileModal;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/RequestList.jsx.html b/coverage/src/components/RequestList.jsx.html new file mode 100644 index 0000000..0553446 --- /dev/null +++ b/coverage/src/components/RequestList.jsx.html @@ -0,0 +1,424 @@ + + + + + + Code coverage report for src/components/RequestList.jsx + + + + + + + + + +
+
+

All files / src/components RequestList.jsx

+
+ +
+ 0% + Statements + 0/93 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/93 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState } from 'react';
+import { DropdownButton, Dropdown, Card, Badge } from 'react-bootstrap';
+import { useDbData } from '../utilities/firebase';
+import "./RequestList.css";
+import AcceptRequestModal from "./Modal"
+import CountdownTimer from './CountdownTimer';
+import DistanceMatrix from '../utilities/Dynamic_Distance';
+ 
+const RequestList = () => {
+    const [sortBy, setSortBy] = useState('timeRemaining');
+    const [show, setShow] = useState(false);
+    const [currentRequest, setCurrentRequest] = useState(null);
+ 
+    // Use useDbData hook to get the users and requests from Firebase
+    const [requestsData, requestsError] = useDbData('/requests');
+    const [usersData, usersError] = useDbData('/users');
+ 
+    if (requestsError || usersError) {
+        return <div>Error loading data!</div>;
+    }
+ 
+    if (!requestsData || !usersData) {
+        return <div>Loading...</div>;
+    }
+ 
+    const requests = Object.values(requestsData); // Convert requests object to array
+    const users = usersData;
+    const requestsNotAccepted = requests.filter(request => request.request_status === "Open");
+ 
+    const handleClose = () => setShow(false);
+    const handleShow = (request) => {
+        setCurrentRequest(request);
+        setShow(true);
+    };
+ 
+    // Remove duplicates from delivery preferences
+    const getUniqueDeliveryPrefs = (prefs) => {
+        return [...new Set(prefs)];
+    };
+ 
+    // Return the correct color based on the delivery preference
+    const getBadgeColor = (pref) => {
+        switch (pref) {
+            case 'Drop off':
+                return 'primary'; // Blue
+            case 'Meet up':
+                return 'success'; // Green
+            case 'Pick up':
+                return 'warning'; // Yellow
+            default:
+                return 'secondary'; // Gray unexpected values
+        }
+    };
+ 
+    return (
+        <div className="w-100">
+            <div className="request-list-header d-flex justify-content-center align-items-center mb-3">
+                <h2 className="mb-0">Request List</h2>
+            </div>
+ 
+            <div className="flex-grow-1 overflow-auto px-3 py-2">
+                <div className="row">
+                    {requestsNotAccepted.map(request => {
+                        const user = users[request.userid]; // Get user info for each request
+                        const rating = user ? user.rate_score : 0;
+                        const uniquePrefs = getUniqueDeliveryPrefs(request.delivery_pref);
+ 
+                        const address = user ? user.Address : '';
+                        const city = user ? user.City : '';
+                        const state = user ? user.StateLoc : '';
+                        const zip = user ? user.Zip : '';
+                        const fullAddress = `${address}, ${city}, ${state} ${zip}`;
+ 
+                        return (
+                            <div key={request.request_id} className="col-12 mb-3">
+                                <Card className="shadow border-0 cursor-pointer hover-effect" onClick={() => handleShow(request)}>
+                                    <Card.Body className="p-0">
+                                        <Card.Header className="d-flex justify-content-between align-items-center text-muted">
+                                            <CountdownTimer request={request} />
+                                            <div>
+                                                {uniquePrefs.map((pref, index) => (
+                                                    <Badge key={index} bg={getBadgeColor(pref)} className="ms-1">
+                                                        {pref}
+                                                    </Badge>
+                                                ))}
+                                            </div>
+                                        </Card.Header>
+                                        <div className="p-3">
+                                            <div className="d-flex justify-content-between align-items-center mb-1">
+                                                <Card.Title className="h5 mb-0">{user?.username || "Unknown"}</Card.Title>
+                                                <div className="d-flex align-items-center">
+                                                    <i className="bi bi-star-fill text-warning me-1"></i>
+                                                    <span>{rating}</span>
+                                                </div>
+                                            </div>
+                                            <DistanceMatrix arrival={fullAddress} />
+                                            <Card.Text>{request.description}</Card.Text>
+                                        </div>
+                                    </Card.Body>
+                                </Card>
+                            </div>
+                        );
+                    })}
+                </div>
+            </div>
+ 
+            {/* Modal outside the map to avoid rendering multiple modals */}
+            {currentRequest && <AcceptRequestModal show={show} handleClose={handleClose} currentRequest={currentRequest}/>}
+        </div>
+    );
+};
+ 
+export default RequestList;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/Request_Page_List.jsx.html b/coverage/src/components/Request_Page_List.jsx.html new file mode 100644 index 0000000..202db65 --- /dev/null +++ b/coverage/src/components/Request_Page_List.jsx.html @@ -0,0 +1,1024 @@ + + + + + + Code coverage report for src/components/Request_Page_List.jsx + + + + + + + + + +
+
+

All files / src/components Request_Page_List.jsx

+
+ +
+ 0% + Statements + 0/241 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/241 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState, useEffect } from 'react';
+import { Button, Card, Alert } from 'react-bootstrap';
+import './Request_Page_list.css';
+import { useDbData, useAuthState, useDbRemove, useDbStatusUpdate } from '../utilities/firebase';
+import { buttonCreate } from './buttons_request';
+import RateModal from './rate_modal';
+import ProfileModal from './ProfileModal';
+ 
+const Request_Page_List = () => {
+  // Request hook
+  const [showUserRequests, setShowUserRequests] = useState(true);
+  const [showAlert, setShowAlert] = useState(false);
+ 
+  // Get userid
+  const [user] = useAuthState();
+  const currentUserID = user?.uid;
+ 
+  // Database hook
+  const [requests, error] = useDbData('requests');
+  const [users, usersError] = useDbData('users');
+  const [removeRequest, removeResult] = useDbRemove();
+  const [updateStatus, updateResult] = useDbStatusUpdate();
+ 
+  /// Rating modal handle //////////////////////////
+  const [selectedRequestId, setSelectedRequestId] = useState(null);  // No initial request selected
+  const [isRateModalOpen, setIsRateModalOpen] = useState(false);     // Rate modal closed by default
+  const [selectedUser, setSelectedUser] = useState(null);            // User for profile modal
+  const [contactNumber, setContactNumber] = useState('xxx-xxx-xxxx');
+  const [acceptMsg, setAcceptMsg] = useState('');
+  const [isProfileModalOpen, setIsProfileModalOpen] = useState(false); // Profile modal closed by default
+ 
+  const handleRateModalOpen = (requestId) => {
+    setSelectedRequestId(requestId);  // Set the clicked request ID
+    setIsRateModalOpen(true);         // Open the rate modal
+  };
+ 
+  const handleRateModalClose = () => {
+    setSelectedRequestId(null);       // Clear the selected request
+    setIsRateModalOpen(false);        // Close the rate modal
+  };
+ 
+  const handleCloseRequest = (requestId, acceptUserId, newRating, userId) => {
+    const userToUpdate = users[acceptUserId];  // Get the user data
+    const rateCount = userToUpdate.rate_count || 1;  // Default rate count
+    
+    // Update the request status to 'Closed'
+    updateStatus(`requests/${requestId}`, {
+      request_status: 'Closed',  // Mark request as closed
+    });
+  
+    // Update the accepted user's rating and increment task_CBU
+    updateStatus(`users/${acceptUserId}`, {
+      rate_score: newRating,           // Update the new rating
+      rate_count: rateCount + 1,       // Increment the rate count
+      task_CBU: (userToUpdate.task_CBU || 0) + 1,  // Increment task_CBU
+    });
+  
+    // Update the current user's task_CFU
+    const currentUser = users[userId];  // Fetch the current user
+    updateStatus(`users/${userId}`, {
+      task_CFU: (currentUser.task_CFU || 0) + 1,  // Increment task_CFU
+    });
+  
+    console.log(`Closing request ID: ${requestId}, updated user ${acceptUserId} with new rating: ${newRating}, and incremented task_CBU/CFU`);
+  
+    handleRateModalClose();
+  };
+ 
+  //////////////////////////////////////////////////////////////////
+  ///// Profile modal stuff ////////////////////////////////////////
+  //////////////////////////////////////////////////////////////////
+ 
+  // Open profile modal using requestId to find the requester's profile
+  const handleProfileModalOpen = (requestId) => {
+    const request = requests[requestId];
+    const userId = request.accept_userid; // Get the userId of the person who made the request
+    const user = users[userId];
+    const msg = request.accept_msg;
+    const number = request.accept_phone_number;
+ 
+    if (user) {
+      setSelectedUser(user);
+      setIsProfileModalOpen(true);
+      setAcceptMsg(msg);
+      setContactNumber(number);
+    }
+  };
+ 
+  // Close profile modal
+  const handleProfileModalClose = () => {
+    setSelectedUser(null);
+    setIsProfileModalOpen(false); 
+  };
+ 
+  /////////////////////
+ 
+  useEffect(() => {
+    if (removeResult) {
+      setShowAlert(true);
+      const timer = setTimeout(() => {
+        setShowAlert(false);
+      }, 1000); // Alert will disappear after 1 second
+ 
+      return () => clearTimeout(timer);
+    }
+  }, [removeResult]);
+ 
+  if (error) {
+    return <div>Error: {error.message}</div>; // Handle error
+  }
+  if (usersError) {
+    return <div>Error: {usersError.message}</div>; // Handle users error
+  }
+  if (!requests || !users) {
+    return <div>Loading...</div>; // Show loading until data is fetched
+  }
+ 
+  // Filter requests based on the logged-in user (currentUserID)
+  const userRequests = Object.values(requests).filter(request => request.userid === currentUserID);
+  const acceptedRequests = Object.values(requests).filter(request => request.accept_userid === currentUserID);
+ 
+  // Obtain User Info
+  const getUserById = (userId) => {
+    const user = Object.values(users).find(u => u.userid === userId);
+    return user ? user : "Unknown User";
+  };
+ 
+  const getBadgeClass = (status) => {
+    switch (status) {
+      case 'Open':
+        return 'bg-secondary';
+      case 'Pending':
+        return 'bg-warning';
+      case 'Accepted':
+        return 'bg-success';
+      case 'Done':
+        return 'bg-info';
+      default:
+        return 'bg-secondary'; // Default color for unknown statuses
+    }
+  };
+ 
+  // UI construction
+  return (
+    <div className="container">
+      {/* Top buttons */}
+      <div className="top-buttons">
+        <Button
+          variant={showUserRequests ? "primary" : "outline-primary"}
+          onClick={() => setShowUserRequests(true)}
+          className="me-2"
+        >
+          Your Requests
+        </Button>
+        <Button
+          variant={!showUserRequests ? "primary" : "outline-primary"}
+          onClick={() => setShowUserRequests(false)}
+        >
+          Requests You Accepted
+        </Button>
+      </div>
+      {showAlert && (
+        <Alert variant={removeResult.error ? "danger" : "success"} className="mt-3">
+          {removeResult.message}
+        </Alert>
+      )}
+ 
+      {/* Single content section */}
+      <div className="row">
+        <div className="col-12">
+          {showUserRequests ? (
+            <div>
+              <div className="request-page-list-header">
+                <h2>Your Requests</h2>
+              </div>
+              {userRequests.length > 0 ? (
+ 
+                  userRequests.map((request) => {
+                    const user = getUserById(request.accept_userid); // Retrieve the user object
+                    
+                    return (
+                      <Card key={request.request_id} className="mb-3 shadow-sm">
+                        <Card.Body>
+                          <div className="d-flex justify-content-between">
+                            <div className="text-start">
+                              <strong className="text-highlight">
+                                { user && user.username
+                                  ? <span><strong>{user.username}</strong> has accepted your request:</span>
+                                  : <span><strong>No one</strong> has accepted your request yet</span>}
+                              </strong>
+                              {request.meet_up_loc && 
+                              <div className="text-muted">
+                                <i className="bi bi-geo-alt"></i>
+ 
+                                Meet up location: {request.meet_up_loc}
+                              </div>}
+                              <Card.Text className="normal-text">{request.description}</Card.Text>
+                            </div>
+                            <div className="text-end">
+ 
+                            <span className="me-2 text-muted">Status:</span>
+                            <span className={`badge ${getBadgeClass(request.request_status)} badge-responsive`}>
+                              {request.request_status}
+                              {(request.request_status === 'Pending' || request.request_status === 'Accepted') && ` / ${findDuplicate(request.delivery_pref)}`}
+                            </span>
+                          </div>
+                        </div>
+                        <div className="d-flex justify-content-center mt-3">
+                          {/* Dynamically create buttons based on request status, including Withdraw Help */}
+                          {buttonCreate(request.request_status, request.request_id, request.delivery_pref, removeRequest, updateStatus, handleRateModalOpen, handleProfileModalOpen)}
+                        </div>
+                      </Card.Body>
+                    </Card>
+                  );
+                })
+              ) : (
+                <p>No requests from you yet.</p>
+              )}
+            </div>
+          ) : (
+            <div>
+              <div className="request-page-list-header">
+                <h2>Requests You Accepted</h2>
+              </div>
+              {acceptedRequests.length > 0 ? (
+                acceptedRequests.map((request) => {
+                  const user = getUserById(request.userid);
+                  return (
+                    <Card key={request.request_id} className="mb-3 shadow-sm">
+                      <Card.Body>
+                        <div className="d-flex justify-content-between">
+                          <div>
+                            <strong className="text-highlight">
+                              You have accepted <strong>{user.username}</strong>'s Request
+                            </strong>
+ 
+                            <div className="text-muted">
+                              <i className="bi bi-geo-alt"></i>
+ 
+                              {user.Address}, {user.Apartment}, {user.City}, {user.StateLoc} {user.Zip}
+                            </div>
+                            {request.meet_up_loc && 
+                            <div className="text-muted">
+                              <i className="bi bi-geo-alt"></i>
+ 
+                              Meet up location: {request.meet_up_loc}
+                            </div>}
+                            {/* Description section */}
+                            <Card.Text className="normal-text">
+                              <strong>Description:</strong> {request.description}
+                            </Card.Text>
+                          </div>
+                          <div className="text-end">
+                            <span className="me-2 text-muted">Status:</span>
+                            <span className={`badge ${getBadgeClass(request.request_status)} badge-responsive`}>
+                              {request.request_status}
+                              {(request.request_status === 'Pending' || request.request_status === 'Accepted') && ` / ${findDuplicate(request.delivery_pref)}`}
+                            </span>
+                          </div>
+ 
+                        </div>
+ 
+                          <div className="d-flex justify-content-center mt-3">
+                            {/* Dynamically create buttons for accepted requests */}
+                            {buttonCreate(request.request_status === 'Closed' ? request.request_status : 'Your_accept', request.request_id,request.delivery_pref, removeRequest, updateStatus,handleRateModalOpen)}
+                          </div>
+                        </Card.Body>
+                      </Card>
+                    );
+                  })
+                ) : (
+                  <p>You haven't accepted any requests yet.</p>
+                )}
+                </div>
+ 
+          )}
+        </div>
+      </div>
+      <RateModal
+        show={isRateModalOpen}
+        handleClose={handleRateModalClose}
+        requestId={selectedRequestId}
+        handleSubmit={handleCloseRequest}
+        requests={requests}
+        users={users}
+      />
+      <ProfileModal
+        show={isProfileModalOpen}
+        handleClose={handleProfileModalClose}
+        user={selectedUser}
+        msg={acceptMsg}
+        contactNumber={contactNumber}
+      />
+    </div>
+  );
+};
+ 
+const findDuplicate = (array) => {
+  const counts = {};
+  array.forEach((item) => {
+    counts[item] = (counts[item] || 0) + 1;
+  });
+ 
+  for (let item in counts) {
+    if (counts[item] > 1) {
+      return item;
+    }
+  }
+  return "Invalid";
+};
+ 
+ 
+export default Request_Page_List;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/buttons_request.jsx.html b/coverage/src/components/buttons_request.jsx.html new file mode 100644 index 0000000..d7fe0b3 --- /dev/null +++ b/coverage/src/components/buttons_request.jsx.html @@ -0,0 +1,370 @@ + + + + + + Code coverage report for src/components/buttons_request.jsx + + + + + + + + + +
+
+

All files / src/components buttons_request.jsx

+
+ +
+ 0% + Statements + 0/68 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/68 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState, useEffect } from 'react';
+import { Button, Card, Alert } from 'react-bootstrap';
+ 
+ 
+ 
+// Function to create buttons based on request status and connect handlers
+export const buttonCreate = (status, requestId, delivery_pref, withdrawHook, statusHook, modalhook, profileModalHook) => {
+  switch (status) {
+    case 'Open':
+      return (
+        <>
+          <Button variant="danger" size="sm" onClick={() => handleWithdrawRequest(requestId,withdrawHook)}>
+            Withdraw Request
+          </Button>
+        </>
+      );
+ 
+    case 'Pending':
+      return (
+        <>
+          <Button variant="success" size="sm" className="me-2" onClick={() => handleAcceptHelp(requestId,statusHook)}>
+            Accept Help
+          </Button>
+          <Button variant="danger" size="sm" className="me-2" onClick={() => handleWithdrawHelp(requestId,statusHook,delivery_pref)}>
+            Reject Help
+          </Button>
+          <Button variant="info" size="sm" onClick={() => profileModalHook(requestId)}>
+            View Profile
+          </Button>
+        </>
+      );
+ 
+    case 'Accepted':
+      return (
+        <>
+          <Button variant="success" size="sm" className="me-2" onClick={() => modalhook(requestId)}>
+            Close Request
+          </Button>
+          <Button variant="info" size="sm"  className="me-2" onClick={() => profileModalHook(requestId)}>
+            View Profile
+          </Button>
+          <Button variant="danger" size="sm" onClick={() => handleWithdrawRequest(requestId,withdrawHook)}>
+            Withdraw Request
+          </Button>
+          
+        </>
+      );
+ 
+    case 'Closed':
+      return null; // No buttons for "Closed" status
+    case 'Your_accept':
+      return (
+        <>
+          <Button variant="danger" size="sm" onClick={() => handleWithdrawHelp(requestId,statusHook,delivery_pref)}>
+            Withdraw Help
+          </Button>
+        </>
+      );
+ 
+    default:
+      return null; // No buttons for unknown status
+  }
+};
+ 
+// Event Handlers (inside the same file)
+const handleWithdrawRequest = (requestId,withdrawHook) => {
+  console.log(`Withdrawing request for request ID: ${requestId}`);
+  withdrawHook(`requests/${requestId}`);
+};
+ 
+const handleAcceptHelp = (requestId,statusHook) => {
+  console.log(`Accepting help for request ID: ${requestId}`);
+  statusHook(`requests/${requestId}`, {
+    request_status : 'Accepted'
+  });
+};
+ 
+const handleWithdrawHelp = (requestId,statusHook,delivery_pref) => {
+  console.log(`Withdrawing help for request ID: ${requestId}`);
+  const uniqueDeliveryPref = [...new Set(delivery_pref)];
+  statusHook(`requests/${requestId}`, {
+    request_status: 'Open',
+    accept_status: false,
+    accept_userid: '',
+    delivery_pref: uniqueDeliveryPref
+  });
+};
+const handleViewProfile = (requestId) => {
+  console.log(`Viewing profile for request ID: ${requestId}`);
+  // Implement view profile logic here
+};
+ 
+const handleCloseRequest = (requestId) => {
+  console.log(`Closing request for request ID: ${requestId}`);
+  // Implement close request logic here
+};
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/index.html b/coverage/src/components/index.html new file mode 100644 index 0000000..4e53e56 --- /dev/null +++ b/coverage/src/components/index.html @@ -0,0 +1,326 @@ + + + + + + Code coverage report for src/components + + + + + + + + + +
+
+

All files src/components

+
+ +
+ 4.62% + Statements + 48/1037 +
+ + +
+ 26.31% + Branches + 5/19 +
+ + +
+ 16.66% + Functions + 3/18 +
+ + +
+ 4.62% + Lines + 48/1037 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Buttons.jsx +
+
42.85%15/3566.66%2/350%2/442.85%15/35
CountdownTimer.jsx +
+
0%0/430%0/10%0/10%0/43
EditProfileModal.jsx +
+
5.71%6/105100%0/00%0/15.71%6/105
Form.jsx +
+
0%0/2440%0/10%0/10%0/244
Header.jsx +
+
0%0/160%0/10%0/10%0/16
Modal.jsx +
+
0%0/160%0/10%0/10%0/16
Navigation.jsx +
+
0%0/270%0/10%0/10%0/27
ProfileModal.jsx +
+
0%0/250%0/10%0/10%0/25
RequestList.jsx +
+
0%0/930%0/10%0/10%0/93
Request_Page_List.jsx +
+
0%0/2410%0/10%0/10%0/241
buttons_request.jsx +
+
0%0/680%0/10%0/10%0/68
rate_modal.jsx +
+
0%0/520%0/10%0/10%0/52
rating.jsx +
+
0%0/330%0/10%0/10%0/33
renderStars.jsx +
+
100%16/1660%3/5100%1/1100%16/16
starRate.jsx +
+
47.82%11/23100%0/00%0/147.82%11/23
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/CreateUser.jsx.html b/coverage/src/components/pages/CreateUser.jsx.html new file mode 100644 index 0000000..7b40430 --- /dev/null +++ b/coverage/src/components/pages/CreateUser.jsx.html @@ -0,0 +1,502 @@ + + + + + + Code coverage report for src/components/pages/CreateUser.jsx + + + + + + + + + +
+
+

All files / src/components/pages CreateUser.jsx

+
+ +
+ 5.3% + Statements + 6/113 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 5.3% + Lines + 6/113 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +1401x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x
// EditProfile.jsx
+import React from 'react'
+import { useState, useEffect } from 'react';
+import './CreateUser.css'
+import { useDbAdd, useDbData, useDbUpdate } from '../../utilities/firebase';
+ 
+ 
+function CreateUser({ closeModal, userId }) {
+    // const [userData, userDataError] = useDbData(`/users/${userId}`);
+ 
+    const [name, setName] = useState('');
+    const [address, setAddress] = useState('');
+    const [apartment, setApartment] = useState('');
+    const [city, setCity] = useState('');
+    const [state, setStateLoc] = useState('');
+    const [zip, setZip] = useState('');
+    const [neighborhoodCode, setNeighborhoodCode] = useState('');
+ 
+    const [addUser, result] = useDbAdd(`/users`);
+ 
+ 
+    const handleSave = async () => {
+        if (!userId) {
+            console.error("User ID is missing.");
+            return;
+        }
+ 
+        const newUserProfile = {
+            username: name,
+            Address: address,
+            Apartment: apartment,
+            City: city,
+            StateLoc: state,
+            Zip: zip,
+            NeighborhoodCode: neighborhoodCode,
+            userid: userId,  // Use the passed-in userId
+            rate_count: 0,
+            rate_score: 0,
+            task_CBU: 0,
+            task_CFU: 0,
+            photo_url: '',
+            location: ''
+        };
+ 
+        try {
+            await addUser(newUserProfile, userId);  // Store user data under /users/{userId}
+            console.log("Profile created successfully!");
+            closeModal();
+        } catch (error) {
+            console.error("Error creating profile:", error);
+        }
+    };
+ 
+ 
+ 
+ 
+    return (
+        <div className='modalBackground'>
+            <div className="modalContainer">
+                {/* <div className="titleCloseBtn">
+                    <button onClick={() => closeModal(false)}> X </button>
+                </div> */}
+                <div className="title">
+                    <h1>Edit Profile</h1>
+                </div>
+                <div className="body">
+                    <div>
+                        <h2>Full Name</h2>
+                        <input
+                            className="input fullWidth"
+                            value={name}
+                            onChange={(e) => setName(e.target.value)}
+                            placeholder="Enter your full name"
+                        />
+                    </div>
+ 
+ 
+                    <div className="address">
+                        <h2>Home Address</h2>
+                        <input
+                            className="input fullWidth"
+                            value={address}
+                            onChange={(e) => setAddress(e.target.value)}
+                            placeholder="Enter your address"
+                        />
+                        <input
+                            className="input fullWidth"
+                            value={apartment}
+                            onChange={(e) => setApartment(e.target.value)}
+                            placeholder="Apartment, suite, etc."
+                        />
+                        <input
+                            className="input fullWidth"
+                            value={city}
+                            onChange={(e) => setCity(e.target.value)}
+                            placeholder="City"
+                        />
+ 
+                        <div className="twoColumns">
+                            <input
+                                className="input"
+                                value={state}
+                                onChange={(e) => setStateLoc(e.target.value)}
+                                placeholder="State"
+                            />
+                            <input
+                                className="input"
+                                value={zip}
+                                onChange={(e) => setZip(e.target.value)}
+                                placeholder="Zip Code"
+                            />
+                        </div>
+                    </div>
+ 
+ 
+                    <div className="neighborhood">
+                        <h2> Neighborhood Code</h2>
+                        {/* <input className="input" value="Enter Here"/> */}
+                        <input
+                            className="input"
+                            value={neighborhoodCode}
+                            onChange={(e) => setNeighborhoodCode(e.target.value)}
+                            placeholder="Enter Neighborhood Code"
+                        />
+                        <p className="smallText">please allow up to 2 days for approval from your Neighborhood Admin</p>
+                    </div>
+ 
+                </div>
+                <div className="footer">
+                    <button  id='cancelBtn' onClick={() => closeModal(false)}>Cancel</button>
+                    <button id='saveBtn' onClick={handleSave} >Save Profile</button>
+                </div>
+ 
+ 
+            </div>
+        </div>
+    )
+}
+ 
+export default CreateUser
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/HomePage.jsx.html b/coverage/src/components/pages/HomePage.jsx.html new file mode 100644 index 0000000..3cdeef9 --- /dev/null +++ b/coverage/src/components/pages/HomePage.jsx.html @@ -0,0 +1,175 @@ + + + + + + Code coverage report for src/components/pages/HomePage.jsx + + + + + + + + + +
+
+

All files / src/components/pages HomePage.jsx

+
+ +
+ 0% + Statements + 0/23 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/23 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState } from 'react';
+import { useNavigate } from 'react-router-dom';
+import "./HomePage.css";
+ 
+import RequestList from "../RequestList"
+import TextOnlyForm from "../Form";
+import { GreenButton } from "../Buttons";
+ 
+const HomePage = () => {
+  const [description, setDescription] = useState(''); 
+  const navigate = useNavigate();
+ 
+  const DirectToRequestForm = () => {
+    navigate('/requestform', { state: { description } }); // Pass description as state
+  };
+ 
+  return (
+    <div className='homepage-container'>
+      <div className='new-request-area'> 
+        <h2 className="mb-0 me-2">New Request</h2>
+        <TextOnlyForm text={description} setText={setDescription} placeholder={'How can your neighbors help?'}/>
+        <GreenButton onClick={DirectToRequestForm} text={'Submit'}/>
+      </div>
+ 
+      <RequestList />
+    </div>
+  );
+};
+ 
+export default HomePage;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/ProfilePage.jsx.html b/coverage/src/components/pages/ProfilePage.jsx.html new file mode 100644 index 0000000..d225433 --- /dev/null +++ b/coverage/src/components/pages/ProfilePage.jsx.html @@ -0,0 +1,421 @@ + + + + + + Code coverage report for src/components/pages/ProfilePage.jsx + + + + + + + + + +
+
+

All files / src/components/pages ProfilePage.jsx

+
+ +
+ 64% + Statements + 48/75 +
+ + +
+ 27.27% + Branches + 3/11 +
+ + +
+ 20% + Functions + 1/5 +
+ + +
+ 64% + Lines + 48/75 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +1131x +1x +1x +1x +1x +1x +1x +1x +1x +  +1x +  +1x +  +1x +  +1x +  +  +1x +1x +1x +  +1x +  +1x +1x +  +  +  +1x +  +  +1x +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +1x +1x +  +1x +  +  +  +1x +1x +1x +1x +1x +  +  +1x +1x +1x +1x +1x +1x +1x +1x +1x +  +1x +  +1x +  +1x +  +1x +  +  +  +  +  +  +  +1x +1x +  +1x +  +1x + 
import React from 'react';
+import "./ProfilePage.css"
+import AuthButton from '../Buttons';
+import StarRate from "../starRate";
+import { useState, useEffect } from "react";
+import { renderStars } from "../renderStars";
+import { useAuthState, useDbData } from '../../utilities/firebase';
+import EditProfileModal from '../EditProfileModal'
+import CreateUser from './CreateUser'
+ 
+const ProfilePage = () => {
+ 
+  const [user] = useAuthState();
+ 
+  const user_id = user ? user.uid : "TESTING";
+ 
+  const [usersData, usersError] = useDbData(`users/${user_id}`);
+ 
+  //for waiting until the information is fetched. If timed out, then go to create Profile button.
+  const [isDelayed, setIsDelayed] = useState(false); // State to control delayed rendering
+  useEffect(() => {
+    const timer = setTimeout(() => {
+      setIsDelayed(true); 
+    }, 5000); 
+ 
+    return () => clearTimeout(timer); 
+  }, []);
+ 
+ 
+  //edit profile
+  const [openModal, setOpenModal] = useState(false);
+ 
+ 
+  if (!user) {
+    return <p>Loading user data...</p>;
+  }
+ 
+  if (!usersData) {
+    return (
+      <div className='profile-page'>
+        {isDelayed ? (
+          <>
+          <p>It seems like you have not made an account with us. Create your profile now!</p>
+          {/* <AuthButton/> */}
+          <button className="white-custom-button" onClick={() => {
+          setOpenModal(true);
+        }}>Create Profile</button>
+ 
+        {openModal && (
+          <CreateUser
+            closeModal={() => setOpenModal(false)}
+            userId={user_id} // Pass userId to CreateUser to create profile with correct ID
+          />
+ 
+        )}
+      </> ) : (
+        <p>Loading user data...</p>
+      )}
+      {/* <AuthButton/> */}
+ 
+      </div>
+    )
+    // return <p>User data loaded!</p>; 
+  }
+  if (!user) {
+    return <p>Loading user data...</p>;
+  }
+ 
+  return (
+    <div className='profile-page'>
+      {/* should be replaced with auth data */}
+      <h1 className="user-name"> {user.displayName || "Anonymous"} </h1>  
+      {/* Ensure user.displayName is not undefined */}
+ 
+      {/* Stars */}
+      <div className="starStyle">
+        { renderStars(usersData.rate_score) }  {/* Pass the rateScore to renderStars */}
+        <p className="ratingCountContainer">{usersData.rate_count}</p>
+      </div>
+      <p className="goodNeighborRanking">Good Neightbor Rating</p>
+ 
+      {/* Stats */}
+      <div className="StatsContainer">
+        <div className="TasksBy">
+          <h1 className="numberStyle"> {usersData.task_CBU || 0} </h1>
+          <p className="textStyle">Tasks completed by User </p>
+        </div>
+        <div className="TasksFor">
+          <h1 className="numberStyle">{usersData.task_CFU || 0}</h1>
+          <p className="textStyle">Tasks completed for User </p>
+        </div>
+ 
+      </div>
+         {/* //edit profile button */}
+         <button className="white-custom-button" onClick={() => {
+            setOpenModal(true);
+          }}> Edit Profile</button>
+ 
+          {openModal && (
+            <EditProfileModal
+              closeModal={() => setOpenModal(false)}
+              userId={user_id} //passes userId to modal, avoids redundancy
+              // initialLocation={usersData.location} // Pass user's initial location
+            />
+          )}      
+ 
+      <AuthButton/>
+    </div>
+  );
+};
+ 
+export default ProfilePage;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/RequestForm.jsx.html b/coverage/src/components/pages/RequestForm.jsx.html new file mode 100644 index 0000000..df88ece --- /dev/null +++ b/coverage/src/components/pages/RequestForm.jsx.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/components/pages/RequestForm.jsx + + + + + + + + + +
+
+

All files / src/components/pages RequestForm.jsx

+
+ +
+ 0% + Statements + 0/69 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/69 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+import { useFormData } from '../../utilities/useFormData';
+import { useDbAdd } from '../../utilities/firebase';
+import './RequestForm.css'
+import { useAuthState } from '../../utilities/firebase';
+import { useLocation, useNavigate } from 'react-router-dom';
+import { push, ref } from 'firebase/database';
+import { database } from '../../utilities/firebase';
+import { RequestForm } from "../Form";
+import { useState, useEffect } from 'react';
+ 
+const RequestFormPage = () => {
+  const location = useLocation();
+  const navigate = useNavigate(); 
+ 
+  useEffect(() => {
+    const { description } = location.state || {};
+    console.log(description)
+    if (description) {
+        setDescription(description);
+    }
+  }, [location.state]);
+ 
+  const [formState] = useFormData(null);
+  const [add] = useDbAdd('requests');
+  const [user] = useAuthState();
+ 
+  const newRequestId = push(ref(database, 'requests')).key;
+ 
+  const [description, setDescription] = useState('');
+  const [timer, setTimer] = useState('');
+  const [deliveryPref, setDeliveryPref] = useState('');
+  const [meetUpLocation, setMeetUpLocation] = useState('');
+ 
+  const data = {
+    description: description,
+    timer: timer, 
+    delivery_pref: deliveryPref,
+    expected_duration: "",
+    request_status: "Open",
+    accept_userid: "",
+    location: "",
+    post_time: new Date().toISOString(),
+    meet_up_loc: meetUpLocation,
+    request_id: newRequestId,
+    userid: user ? user.uid : "TESTING",
+    username: user ? user.displayName || "Anonymous" : "Anonymous",
+  };
+ 
+ 
+  const submit = async (evt) => {
+    evt.preventDefault();
+ 
+    const errors = formState.errors || {};
+ 
+    if (Object.keys(errors).length === 0) {
+      try {
+        await add({ ...data }, newRequestId);
+        console.log('Form submitted:', { ...data });
+        navigate('/'); // Navigate back to homepage after successful submission
+      } catch (error) {
+        console.error("Error saving data:", error);
+      }
+    }
+  };
+ 
+  return (
+    <div className="requestform-page">
+      <h4 className='title'>New Request</h4>
+      <RequestForm 
+        data={data} 
+        setDescription={setDescription} 
+        setTimer={setTimer} 
+        deliveryPref={deliveryPref}
+        setDeliveryPref={setDeliveryPref}
+        setMeetUpLocation={setMeetUpLocation}
+        onClick={submit}/>
+    </div>
+  );
+};
+ 
+export default RequestFormPage;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/RequestsPage.jsx.html b/coverage/src/components/pages/RequestsPage.jsx.html new file mode 100644 index 0000000..0984638 --- /dev/null +++ b/coverage/src/components/pages/RequestsPage.jsx.html @@ -0,0 +1,127 @@ + + + + + + Code coverage report for src/components/pages/RequestsPage.jsx + + + + + + + + + +
+
+

All files / src/components/pages RequestsPage.jsx

+
+ +
+ 0% + Statements + 0/11 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+import './RequestsPage.css'
+import Request_Page_List from "../Request_Page_List"
+ 
+const RequestsPage = () => {
+  return (
+    <div className="requests-page-container">
+      <h1>Your Requests Dashboard</h1>
+      <Request_Page_List />
+    </div>
+  );
+};
+ 
+export default RequestsPage;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/SignIn.jsx.html b/coverage/src/components/pages/SignIn.jsx.html new file mode 100644 index 0000000..8639254 --- /dev/null +++ b/coverage/src/components/pages/SignIn.jsx.html @@ -0,0 +1,136 @@ + + + + + + Code coverage report for src/components/pages/SignIn.jsx + + + + + + + + + +
+
+

All files / src/components/pages SignIn.jsx

+
+ +
+ 0% + Statements + 0/16 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import Navigation from '../Navigation.jsx';
+import "./SignIn.css"
+import AuthButton from '../Buttons.jsx'
+ 
+const SignInPage = () => {
+    return (
+        <div className="sign-in-page">
+            <div className="sign-in">
+                <div className="house">
+                    <i class="bi bi-house-door"></i>
+                </div>
+                <p className="title">Good Neighbor</p>
+                <AuthButton />
+            </div>
+        </div>
+    );
+}
+export default SignInPage;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/pages/index.html b/coverage/src/components/pages/index.html new file mode 100644 index 0000000..23e1b82 --- /dev/null +++ b/coverage/src/components/pages/index.html @@ -0,0 +1,191 @@ + + + + + + Code coverage report for src/components/pages + + + + + + + + + +
+
+

All files src/components/pages

+
+ +
+ 17.58% + Statements + 54/307 +
+ + +
+ 20% + Branches + 3/15 +
+ + +
+ 10% + Functions + 1/10 +
+ + +
+ 17.58% + Lines + 54/307 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
CreateUser.jsx +
+
5.3%6/113100%0/00%0/15.3%6/113
HomePage.jsx +
+
0%0/230%0/10%0/10%0/23
ProfilePage.jsx +
+
64%48/7527.27%3/1120%1/564%48/75
RequestForm.jsx +
+
0%0/690%0/10%0/10%0/69
RequestsPage.jsx +
+
0%0/110%0/10%0/10%0/11
SignIn.jsx +
+
0%0/160%0/10%0/10%0/16
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/rate_modal.jsx.html b/coverage/src/components/rate_modal.jsx.html new file mode 100644 index 0000000..f82122a --- /dev/null +++ b/coverage/src/components/rate_modal.jsx.html @@ -0,0 +1,322 @@ + + + + + + Code coverage report for src/components/rate_modal.jsx + + + + + + + + + +
+
+

All files / src/components rate_modal.jsx

+
+ +
+ 0% + Statements + 0/52 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/52 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState } from 'react';
+import { Modal, Button } from 'react-bootstrap';
+import Rating from './rating';  
+ 
+const RateModal = ({ show, handleClose, requestId, handleSubmit, requests, users }) => {
+  const [rating, setRating] = useState(0);  // State to store the rating
+ 
+  // Find the specific request based on requestId
+  const request = requests ? requests[requestId] : null;
+ 
+  // Get the accept_userid from the request
+  const acceptUserId = request ? request.accept_userid : null;
+ 
+  // Find the user's username based on accept_userid
+  const neighborUsername = acceptUserId && users[acceptUserId] ? users[acceptUserId].username : 'Unknown User';
+ 
+  const requestDescription = request ? request.description : 'No description available';
+ 
+  const handleRatingSubmit = () => {
+    const userToUpdate = users[acceptUserId];  // Get the user from users data
+  
+    const currentRating = userToUpdate.rate || 5;  // Current user rating (default to 5 if not available)
+    const rateCount = userToUpdate.rate_count || 1;  // Current rate count (default to 1 if not available)
+    const currentUserId = request.userid;
+    // Calculate the new rating
+    let newRating = (currentRating * rateCount + rating) / (rateCount + 1);  
+    newRating = parseFloat(newRating.toFixed(2)); 
+    // Pass data to the parent through handleSubmit
+    handleSubmit(requestId, acceptUserId, newRating,currentUserId);  // Pass new rating, userId, and requestId
+    handleClose();  // Close modal
+  };
+ 
+  return (
+    <Modal show={show} onHide={handleClose} centered>
+      <Modal.Header closeButton>
+        <Modal.Title className="text-center w-100">Please rate your neighbor's help</Modal.Title>
+      </Modal.Header>
+      <Modal.Body>
+        {requestId ? (
+          <div className="text-center">
+            {/* Neighbor Username */}
+            <div className="mb-3">
+              <strong className="fw-bold">Neighbor Username:</strong>
+              <p className="text-muted">{neighborUsername}</p>
+            </div>
+ 
+            {/* Request Description */}
+            <div className="mb-3">
+              <strong className="fw-bold">Request Description:</strong>
+              <p className="text-muted">{requestDescription}</p>
+            </div>
+ 
+            {/* Rating Section */}
+            <div className="rating-section">
+              <p className="fw-bold">Please rate your neighbor's help:</p>
+              <Rating onRate={(rate) => setRating(rate)} /> 
+            </div>
+          </div>
+        ) : (
+          <p>No request selected.</p>
+        )}
+      </Modal.Body>
+      <Modal.Footer className="d-flex justify-content-center">
+        {/* Submit button centered */}
+        <Button 
+          variant="primary" 
+          onClick={handleRatingSubmit}  // Handle the submit action and rating
+        >
+          Submit
+        </Button>
+      </Modal.Footer>
+    </Modal>
+  );
+};
+ 
+export default RateModal;
+ 
+ 
+ 
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/rating.jsx.html b/coverage/src/components/rating.jsx.html new file mode 100644 index 0000000..d640e1c --- /dev/null +++ b/coverage/src/components/rating.jsx.html @@ -0,0 +1,217 @@ + + + + + + Code coverage report for src/components/rating.jsx + + + + + + + + + +
+
+

All files / src/components rating.jsx

+
+ +
+ 0% + Statements + 0/33 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/33 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState } from 'react';
+ 
+// Inline SVG for the star
+const starSvg = (filled) => (
+  <svg
+    xmlns="http://www.w3.org/2000/svg"
+    width="40"
+    height="40"
+    viewBox="0 0 30 30"
+    fill={filled ? "#ffc107" : "#e4e5e9"}  // Fill color based on hover or rating
+  >
+    <path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.707 1.629 8.111L12 18.896l-7.565 4.228L6.064 15.01 0 9.303l8.332-1.148z" />
+  </svg>
+);
+ 
+const Rating = ({ onRate }) => {
+  const [rating, setRating] = useState(5);  // Actual rating value
+  const [hover, setHover] = useState(null); // Hover state for stars
+ 
+  return (
+    <div className="star-rating d-flex justify-content-center">
+      {[...Array(5)].map((star, index) => {
+        const ratingValue = index + 1;
+ 
+        return (
+          <span
+            key={index}
+            style={{ cursor: "pointer" }}
+            onClick={() => {
+              setRating(ratingValue);  // Update the rating value
+              onRate(ratingValue);     // Pass rating to parent component
+            }}
+            onMouseEnter={() => setHover(ratingValue)}  // Set hover state when mouse enters a star
+            onMouseLeave={() => setHover(null)}         // Reset hover state when mouse leaves
+          >
+            {starSvg(ratingValue <= (hover || rating))}  {/* Render filled or empty star */}
+          </span>
+        );
+      })}
+    </div>
+  );
+};
+ 
+export default Rating;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/renderStars.jsx.html b/coverage/src/components/renderStars.jsx.html new file mode 100644 index 0000000..381ac60 --- /dev/null +++ b/coverage/src/components/renderStars.jsx.html @@ -0,0 +1,154 @@ + + + + + + Code coverage report for src/components/renderStars.jsx + + + + + + + + + +
+
+

All files / src/components renderStars.jsx

+
+ +
+ 100% + Statements + 16/16 +
+ + +
+ 60% + Branches + 3/5 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 16/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +241x +  +  +1x +1x +1x +1x +  +1x +1x +  +1x +4x +1x +  +1x +  +1x +1x +1x +1x +  +1x + 
import React from 'react';
+ 
+// Function to render stars based on the rating
+export const renderStars = (rating) => {
+  const fullStars = Math.floor(rating);
+  const halfStar = rating % 1 !== 0 ? 1 : 0;
+  const emptyStars = 5 - fullStars - halfStar;
+ 
+  return (
+    <>
+      {/* Full stars */}
+      {[...Array(fullStars)].map((_, index) => (
+        <i key={index} className="bi bi-star-fill text-warning"></i>
+      ))}
+      {/* Half star */}
+      {halfStar ? <i className="bi bi-star-half text-warning"></i> : null}
+      {/* Empty stars */}
+      {[...Array(emptyStars)].map((_, index) => (
+        <i key={index} className="bi bi-star text-warning"></i>
+      ))}
+    </>
+  );
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/components/starRate.jsx.html b/coverage/src/components/starRate.jsx.html new file mode 100644 index 0000000..9e5363a --- /dev/null +++ b/coverage/src/components/starRate.jsx.html @@ -0,0 +1,190 @@ + + + + + + Code coverage report for src/components/starRate.jsx + + + + + + + + + +
+
+

All files / src/components starRate.jsx

+
+ +
+ 47.82% + Statements + 11/23 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 47.82% + Lines + 11/23 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +361x +  +  +  +1x +1x +1x +1x +1x +1x +1x +  +1x +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+// import {FaStar} from "react-icons/fa";
+ 
+ 
+const starSvg = (
+    <svg
+      xmlns="http://www.w3.org/2000/svg"
+      width="40"
+      height="40"
+      viewBox="0 0 30 30"
+      fill="currentColor"
+    >
+      <path d="M12 .587l3.668 7.568 8.332 1.151-6.064 5.707 1.629 8.111L12 18.896l-7.565 4.228L6.064 15.01 0 9.303l8.332-1.148z" />
+    </svg>
+  );
+ 
+  export default function StarRate() {
+ 
+    return (
+      <>
+        {[...Array(5)].map((star, index) => {
+          return (
+            <>
+ 
+            <span key={index}>
+                {starSvg}
+            </span>
+ 
+            </>
+          );
+        })}
+      </>
+    );
+  }
+ 
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/index.html b/coverage/src/index.html new file mode 100644 index 0000000..9ba66b0 --- /dev/null +++ b/coverage/src/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 0% + Statements + 0/64 +
+ + +
+ 0% + Branches + 0/3 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 0% + Lines + 0/64 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
App.jsx +
+
0%0/410%0/10%0/10%0/41
Home.jsx +
+
0%0/130%0/10%0/10%0/13
index.jsx +
+
0%0/100%0/10%0/10%0/10
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/index.jsx.html b/coverage/src/index.jsx.html new file mode 100644 index 0000000..fea5d92 --- /dev/null +++ b/coverage/src/index.jsx.html @@ -0,0 +1,121 @@ + + + + + + Code coverage report for src/index.jsx + + + + + + + + + +
+
+

All files / src index.jsx

+
+ +
+ 0% + Statements + 0/10 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/10 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13  +  +  +  +  +  +  +  +  +  +  +  + 
import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './index.css';
+import App from './App';
+ 
+const root = ReactDOM.createRoot(document.getElementById('root'));
+ 
+root.render(
+  <React.StrictMode>
+    <App />
+  </React.StrictMode>
+);
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utilities/Dynamic_Distance.jsx.html b/coverage/src/utilities/Dynamic_Distance.jsx.html new file mode 100644 index 0000000..b8ec876 --- /dev/null +++ b/coverage/src/utilities/Dynamic_Distance.jsx.html @@ -0,0 +1,319 @@ + + + + + + Code coverage report for src/utilities/Dynamic_Distance.jsx + + + + + + + + + +
+
+

All files / src/utilities Dynamic_Distance.jsx

+
+ +
+ 0% + Statements + 0/63 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/63 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import React, { useState, useEffect } from 'react';
+ 
+const DistanceMatrix = ({ arrival }) => {
+  const [origin, setOrigin] = useState('');
+  const [destination, setDestination] = useState(arrival);
+  const [distance, setDistance] = useState('');
+  const [duration, setDuration] = useState('');
+ 
+  const getUserLocation = () => {
+    if (navigator.geolocation) {
+      navigator.geolocation.getCurrentPosition(
+        (position) => {
+          const { latitude, longitude } = position.coords;
+          setOrigin(`${latitude},${longitude}`);
+        },
+        (error) => {
+          console.error('Error getting location:', error);
+          alert('Unable to retrieve your location. Please enable location services.');
+        }
+      );
+    } else {
+      console.error('Geolocation is not supported by this browser.');
+      alert('Geolocation is not supported by this browser.');
+    }
+  };
+ 
+  const calculateDistance = () => {
+    const service = new window.google.maps.DistanceMatrixService();
+ 
+    service.getDistanceMatrix(
+      {
+        origins: [origin],
+        destinations: [destination],
+        travelMode: window.google.maps.TravelMode.WALKING,
+        unitSystem: window.google.maps.UnitSystem.METRIC,
+        avoidHighways: false,
+        avoidTolls: false,
+      },
+      (response, status) => {
+        if (status !== 'OK') {
+          alert('Error was: ' + status);
+          return;
+        }
+ 
+        const results = response.rows[0].elements[0];
+        if (results) {
+          setDistance(results.distance.text);
+          setDuration(results.duration.text);
+        }
+      }
+    );
+  };
+ 
+  useEffect(() => {
+    getUserLocation();
+  }, []);
+ 
+  useEffect(() => {
+    if (origin && destination) {
+      calculateDistance();
+    }
+  }, [origin, destination]);
+  // console.log('Origin', origin);
+  // console.log('Destination', destination);
+ 
+  return (
+    
+ 
+    <div>
+      <div>
+      <text>{distance} ({duration || 'Calculating...'} away)</text>
+      </div>
+      
+      
+    </div>
+  );
+};
+ 
+export default DistanceMatrix;
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utilities/firebase.js.html b/coverage/src/utilities/firebase.js.html new file mode 100644 index 0000000..7c24a65 --- /dev/null +++ b/coverage/src/utilities/firebase.js.html @@ -0,0 +1,538 @@ + + + + + + Code coverage report for src/utilities/firebase.js + + + + + + + + + +
+
+

All files / src/utilities firebase.js

+
+ +
+ 0% + Statements + 0/151 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/151 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { getDatabase, onValue, ref, update, get, push, set ,remove} from 'firebase/database';
+import { useCallback, useEffect, useState } from 'react';
+import { initializeApp } from 'firebase/app';
+import { getAuth, GoogleAuthProvider, onAuthStateChanged, signInWithPopup, signOut as firebaseSignOut } from 'firebase/auth';
+
+const firebaseConfig = {
+    apiKey: "AIzaSyCPJhTswV7OHI1weBX4_0dMqRSauCwUDJs",
+    authDomain: "yellowteam-goodneigh.firebaseapp.com",
+    databaseURL: "https://yellowteam-goodneigh-default-rtdb.firebaseio.com",
+    projectId: "yellowteam-goodneigh",
+    storageBucket: "yellowteam-goodneigh.appspot.com",
+    messagingSenderId: "157525304360",
+    appId: "1:157525304360:web:5e9cc1389851deb0d3011d",
+    measurementId: "G-VP1GR150E7"
+  };
+
+// Initialize Firebase
+const firebase = initializeApp(firebaseConfig);
+const database = getDatabase(firebase);
+const auth = getAuth(firebase);
+
+export const useDbData = (path) => {
+    const [data, setData] = useState();
+    const [error, setError] = useState(null);
+
+    useEffect(() => (
+        onValue(ref(database, path), (snapshot) => {
+            setData(snapshot.val());
+        }, (error) => {
+            setError(error);
+        })
+    ), [path]);
+
+    return [data, error];
+};
+
+const makeResult = (error) => {
+    const timestamp = Date.now();
+    const message = error?.message || `Updated: ${new Date(timestamp).toLocaleString()}`;
+    return { timestamp, error, message };
+};
+
+export const useDbUpdate = (path) => {
+    const [result, setResult] = useState();
+    const updateData = useCallback(async (value) => {
+        console.log('Updating path:', path);
+        console.log('Value before update:', value);
+
+        if (!value || typeof value !== 'object') {
+            console.error("Invalid value passed to updateData:", value);
+            return;
+        }
+
+        const dbRef = ref(database, path);
+        update(dbRef, value)
+            .then(() => setResult(makeResult()))
+            .catch((error) => {
+                console.error("Error during Firebase update:", error);
+                setResult(makeResult(error));
+            });
+    }, [path]);
+
+    return [updateData, result];
+};
+
+export { firebase, database, auth };
+
+export const signInWithGoogle = () => {
+    signInWithPopup(auth, new GoogleAuthProvider());
+};
+
+export const signOut = () => firebaseSignOut(auth);
+
+export const useAuthState = () => {
+    const [user, setUser] = useState();
+
+    useEffect(() => (
+        onAuthStateChanged(auth, setUser)
+    ), []);
+
+    return [user];
+};
+
+export const useDbAdd = (path) => {
+    const [result, setResult] = useState(null);
+  
+    // Given data and a key, the key is used to create a new path for the data
+    const add = async (data, key) => {
+      try {
+        const newRef = ref(database, `${path}/${key}`); // Use the key passed in the argument
+        await set(newRef, data); // Set data at the specified reference
+        setResult({ message: 'Request added successfully!', error: false });
+      } catch (error) {
+        setResult({ message: error.message, error: true });
+      }
+    };
+  
+    return [add, result];
+  };
+
+  export const getRef = (path) => {
+
+
+
+
+  };
+  export const useDbRemove = () => {
+    const [result, setResult] = useState(null);
+
+    const removeData = useCallback(async (path) => {
+        try {
+            const dbRef = ref(database, path);
+            const snapshot = await get(dbRef);
+            if (snapshot.exists()) {
+                await remove(dbRef);
+                setResult({ message: `Removed successfully`, error: false });
+            } else {
+                setResult({ message: `Error: No data found at path: ${path}`, error: true });
+            }
+        } catch (error) {
+            setResult({ message: error.message, error: true });
+        }
+    }, []);
+
+    return [removeData, result];
+};
+
+
+export const useDbStatusUpdate = () => {
+    const [result, setResult] = useState();
+  
+
+    const updateStatus = useCallback(async (path, updates) => {
+      console.log('Updating path:', path);
+      console.log('Update data:', updates);
+  
+      if (!updates || typeof updates !== 'object') {
+        console.error("Invalid updates passed to updateStatus:", updates);
+        return;
+      }
+  
+      const dbRef = ref(database, path); // Pass the path dynamically
+      update(dbRef, updates)
+        .then(() => setResult({ success: true }))
+        .catch((error) => {
+          console.error("Error during Firebase update:", error);
+          setResult({ success: false, error });
+        });
+    }, []);
+  
+    return [updateStatus, result];
+  };
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utilities/index.html b/coverage/src/utilities/index.html new file mode 100644 index 0000000..9685a43 --- /dev/null +++ b/coverage/src/utilities/index.html @@ -0,0 +1,146 @@ + + + + + + Code coverage report for src/utilities + + + + + + + + + +
+
+

All files src/utilities

+
+ +
+ 0% + Statements + 0/228 +
+ + +
+ 0% + Branches + 0/3 +
+ + +
+ 0% + Functions + 0/3 +
+ + +
+ 0% + Lines + 0/228 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Dynamic_Distance.jsx +
+
0%0/630%0/10%0/10%0/63
firebase.js +
+
0%0/1510%0/10%0/10%0/151
useFormData.jsx +
+
0%0/140%0/10%0/10%0/14
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/src/utilities/useFormData.jsx.html b/coverage/src/utilities/useFormData.jsx.html new file mode 100644 index 0000000..d5f01eb --- /dev/null +++ b/coverage/src/utilities/useFormData.jsx.html @@ -0,0 +1,136 @@ + + + + + + Code coverage report for src/utilities/useFormData.jsx + + + + + + + + + +
+
+

All files / src/utilities useFormData.jsx

+
+ +
+ 0% + Statements + 0/14 +
+ + +
+ 0% + Branches + 0/1 +
+ + +
+ 0% + Functions + 0/1 +
+ + +
+ 0% + Lines + 0/14 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { useState } from 'react';
+ 
+export const useFormData = (validator = null, values = {}) => {
+  const [state, setState] = useState(() => ({ values }));
+ 
+  const change = (evt) => {
+    const { id, value } = evt.target;
+    const error = validator ? validator(id, value) : '';
+    evt.target.setCustomValidity(error);
+    
+    const values = {...state.values, [id]: value};
+    const errors = {...state.errors, [id]: error};
+    const hasError = Object.values(errors).some(x => x !== '');
+    setState(hasError ? { values, errors } : { values });
+  };
+ 
+  return [state, change];
+};
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2e6ab8a..eaa7f6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@eslint/js": "^9.11.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.1", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^4.3.3", "@vitest/coverage-v8": "^2.1.1", "@vitest/ui": "^2.1.1", "eslint": "^9.11.1", @@ -2304,15 +2304,14 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-react": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.1.tgz", - "integrity": "sha512-m/V2syj5CuVnaxcUJOQRel/Wr31FFXRFlnOoq1TVtkCxsY5veGMTEmpWHndrhB2U8ScHtCQB1e+4hWYExQc6Lg==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.3.tgz", + "integrity": "sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/core": "^7.24.5", - "@babel/plugin-transform-react-jsx-self": "^7.24.5", - "@babel/plugin-transform-react-jsx-source": "^7.24.1", + "@babel/core": "^7.25.2", + "@babel/plugin-transform-react-jsx-self": "^7.24.7", + "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@types/babel__core": "^7.20.5", "react-refresh": "^0.14.2" }, diff --git a/package.json b/package.json index 8d8c12a..2c0bc4c 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "@eslint/js": "^9.11.1", "@testing-library/dom": "^10.4.0", "@testing-library/react": "^16.0.1", - "@vitejs/plugin-react": "^4.3.1", + "@vitejs/plugin-react": "^4.3.3", "@vitest/coverage-v8": "^2.1.1", "@vitest/ui": "^2.1.1", "eslint": "^9.11.1", diff --git a/src/App.test.jsx b/src/App.test.jsx deleted file mode 100644 index 6820dcc..0000000 --- a/src/App.test.jsx +++ /dev/null @@ -1,19 +0,0 @@ -import {describe, expect, test} from 'vitest'; -import {fireEvent, render, screen} from '@testing-library/react'; -import App from './App'; - -describe('counter tests', () => { - - test("Counter should be 0 at the start", () => { - render(); - expect(screen.getByText('count is: 0')).toBeDefined(); - }); - - test("Counter should increment by one when clicked", async () => { - render(); - const counter = screen.getByRole('button'); - fireEvent.click(counter); - expect(await screen.getByText('count is: 1')).toBeDefined(); - }); - -}); \ No newline at end of file diff --git a/src/components/pages/ProfilePage-Wang.test.js b/src/components/pages/ProfilePage-Wang.test.js new file mode 100644 index 0000000..013f129 --- /dev/null +++ b/src/components/pages/ProfilePage-Wang.test.js @@ -0,0 +1,63 @@ +// src/components/pages/ProfilePage-Wang.test.js +import { render, screen, fireEvent } from '@testing-library/react'; +import ProfilePage from './ProfilePage'; // Adjust the import path if necessary +import { vi } from 'vitest'; + +// Mocking Firebase authentication and data +vi.mock('../../utilities/firebase', () => ({ + useAuthState: vi.fn().mockReturnValue([null, false]), // No user signed in + useDbData: vi.fn().mockReturnValue([null, false]), // No data from the database +})); + +describe('ProfilePage-Wang', () => { + + it('renders the ProfilePage correctly', () => { + render(); // Render the ProfilePage component + + // Check for static elements on the page + expect(screen.getByText('Profile')).toBeInTheDocument(); + expect(screen.getByText('Good Neighbor')).toBeInTheDocument(); + }); + + it('displays loading state while waiting for data', () => { + render(); // Render the ProfilePage component + + // If there's a loading state while waiting for data, ensure it's visible + expect(screen.getByText('Loading...')).toBeInTheDocument(); + }); + + it('should show a "sign in" button', () => { + render(); + + // Check if the sign-in button is rendered + expect(screen.getByRole('button', { name: /sign in/i })).toBeInTheDocument(); + }); + + it('should display an error message if Firebase authentication fails', () => { + // Simulating a Firebase failure by returning an error in the mock + vi.mock('../../utilities/firebase', () => ({ + useAuthState: vi.fn().mockReturnValue([null, false]), + useDbData: vi.fn().mockImplementation(() => { + throw new Error('Firebase connection failed'); + }), + })); + + render(); + + // Test that an error message is displayed when Firebase fails + expect(screen.getByText('Error loading data')).toBeInTheDocument(); + }); + + it('should handle button click correctly', () => { + render(); + + // Simulate a click on the sign-in button + const button = screen.getByRole('button', { name: /sign in/i }); + fireEvent.click(button); + + // After clicking the button, check if the state changes or UI updates + // For example, you might check for a loading message or state change + expect(screen.getByText('Signing in...')).toBeInTheDocument(); // You can adjust this based on the actual component behavior + }); + +}); diff --git a/src/components/pages/ProfilePage.ziye.test.js b/src/components/pages/ProfilePage.ziye.test.js new file mode 100644 index 0000000..e1720ec --- /dev/null +++ b/src/components/pages/ProfilePage.ziye.test.js @@ -0,0 +1,76 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent, act } from '@testing-library/react'; +import ProfilePage from './ProfilePage'; +import { useAuthState, useDbData, useDbAdd } from '../../utilities/firebase'; + +vi.mock('../../utilities/firebase', () => ({ + useAuthState: vi.fn(), + useDbData: vi.fn(), + useDbAdd: vi.fn(), + signOut: vi.fn() +})); + +describe('User name on profile page', () => { + beforeEach(() => { + useAuthState.mockReturnValue([null]); // Default no user + useDbData.mockReturnValue([null, false]); // Default no data + }); + + it("displays the user's name on the Profile page", () => { + const mockUser = { uid: 'mockUserId', displayName: 'John Doe' }; + useAuthState.mockReturnValue([mockUser]); + useDbData.mockReturnValue([{ rate_score: 4, rate_count: 10, task_CBU: 3, task_CFU: 5 }, null]); + + render(); + + expect(screen.getByText(/John Doe/i)).toBeInTheDocument(); + }); +}); + +describe('ProfilePage - New User Profile Creation', () => { + it("shows 'Create Profile' button for users without profile data and adds user to the mock database on submission", async () => { + const mockUser = { uid: 'newUserId', displayName: 'New User' }; + useAuthState.mockReturnValue([mockUser]); + useDbData.mockReturnValue([null, false]); // Simulate no profile data + + const mockAddUser = vi.fn(); + useDbAdd.mockReturnValue([mockAddUser, null]); + + render(); + + // Check for and click the "Create Profile" button + expect(screen.getByText(/Create Profile/i)).toBeInTheDocument(); + fireEvent.click(screen.getByText(/Create Profile/i)); + + // Fill out form fields + fireEvent.change(screen.getByPlaceholderText(/Enter your full name/i), { target: { value: 'New User' } }); + fireEvent.change(screen.getByPlaceholderText(/Enter your address/i), { target: { value: '123 Main St' } }); + fireEvent.change(screen.getByPlaceholderText(/Apartment, suite, etc./i), { target: { value: 'Apt 4B' } }); + fireEvent.change(screen.getByPlaceholderText(/City/i), { target: { value: 'Metropolis' } }); + fireEvent.change(screen.getByPlaceholderText(/State/i), { target: { value: 'NY' } }); + fireEvent.change(screen.getByPlaceholderText(/Zip Code/i), { target: { value: '12345' } }); + fireEvent.change(screen.getByPlaceholderText(/Enter Neighborhood Code/i), { target: { value: 'NEIGH123' } }); + + // Click "Save Profile" and verify the mockAddUser function call + fireEvent.click(screen.getByText(/Save Profile/i)); + expect(mockAddUser).toHaveBeenCalledWith( + { + username: 'New User', + Address: '123 Main St', + Apartment: 'Apt 4B', + City: 'Metropolis', + StateLoc: 'NY', + Zip: '12345', + NeighborhoodCode: 'NEIGH123', + userid: 'newUserId', + rate_count: 0, + rate_score: 0, + task_CBU: 0, + task_CFU: 0, + photo_url: '', + location: '' + }, + 'newUserId' + ); + }); +}); diff --git a/vite.config.js b/vite.config.js index b312230..c6b55a4 100644 --- a/vite.config.js +++ b/vite.config.js @@ -7,5 +7,8 @@ export default defineConfig({ test: { globals: true, environment: 'jsdom', + coverage: { + reporter: ['text', 'html'], + }, } });