diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..c52a612b44809984baf1b03b2c3f4b89d5501511 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "vscode-pets.position": "explorer" +} \ No newline at end of file diff --git a/et --hard HEAD@{2} b/et --hard HEAD@{2} new file mode 100644 index 0000000000000000000000000000000000000000..080506ee11eb7b72adccd7694b14aa7fc112bc78 --- /dev/null +++ b/et --hard HEAD@{2} @@ -0,0 +1,485 @@ +[33m6234e68[m[33m ([m[1;36mHEAD -> [m[1;32missue/271.1-create-new-backend-routes-connection[m[33m)[m HEAD@{0}: rebase (continue) (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33m6234e68[m[33m ([m[1;36mHEAD -> [m[1;32missue/271.1-create-new-backend-routes-connection[m[33m)[m HEAD@{1}: rebase (continue): rebase +[33m347541c[m HEAD@{2}: rebase (continue): rebase +[33m97779d6[m HEAD@{3}: rebase (continue): ok +[33m05dace3[m HEAD@{4}: rebase (continue) (pick): ok +[33me3b8b31[m HEAD@{5}: rebase (continue) (pick): Issue #287: UPDATE mecred statistics on about page +[33m5e83680[m HEAD@{6}: rebase (continue): Issue #289: ADD Icons on profile page (verified) +[33mcda5c94[m HEAD@{7}: rebase (continue) (pick): Issue #290: FIX Style of the share profile button +[33md893a7a[m HEAD@{8}: rebase (continue) (pick): Issue #291: User card: update information +[33m8d8c38b[m HEAD@{9}: rebase (continue): Issue #288: UPDATE change order icons sidebar +[33m82b17e3[m HEAD@{10}: rebase (pick): Issue #280: FIX remove profile icon offline +[33md70485c[m HEAD@{11}: rebase (pick): FIX Correction of the random color vector and other details, plus removal of statistics +[33mc48bb0a[m HEAD@{12}: rebase (pick): Issue #282: FIX profile edition on HC +[33m0685262[m[33m ([m[1;31morigin/issue/271.gus[m[33m, [m[1;32missue/271.gus[m[33m)[m HEAD@{13}: rebase (start): checkout issue/271.gus +[33mae8f559[m HEAD@{14}: checkout: moving from issue/271.gus to issue/271.1-create-new-backend-routes-connection +[33m0685262[m[33m ([m[1;31morigin/issue/271.gus[m[33m, [m[1;32missue/271.gus[m[33m)[m HEAD@{15}: checkout: moving from issue/271.1-create-new-backend-routes-connection to issue/271.gus +[33mae8f559[m HEAD@{16}: rebase (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33mae8f559[m HEAD@{17}: rebase (pick): rebase +[33mc09911e[m HEAD@{18}: rebase (pick): rebase +[33m6a29086[m HEAD@{19}: rebase (pick): ok +[33m9535dd5[m HEAD@{20}: rebase (pick): ok +[33mc2308b4[m HEAD@{21}: rebase (pick): Issue #287: UPDATE mecred statistics on about page +[33mb624b7b[m HEAD@{22}: rebase (pick): Issue #289: ADD Icons on profile page (verified) +[33m22ea969[m HEAD@{23}: rebase (pick): Issue #290: FIX Style of the share profile button +[33m87445ec[m HEAD@{24}: rebase (pick): Issue #291: User card: update information +[33mf9c42e9[m HEAD@{25}: rebase (pick): Issue #288: UPDATE change order icons sidebar +[33m5d6caf1[m HEAD@{26}: rebase (pick): Issue #280: FIX remove profile icon offline +[33mfa46ddd[m HEAD@{27}: rebase (pick): FIX Correction of the random color vector and other details, plus removal of statistics +[33me5f98f1[m HEAD@{28}: rebase (pick): Issue #282: FIX profile edition on HC +[33m9d10585[m[33m ([m[1;31morigin/issue/271.3-update-routes-new-backend[m[33m, [m[1;32missue/271.3-update-routes-new-backend[m[33m)[m HEAD@{29}: rebase (start): checkout issue/271.3-update-routes-new-backend +[33m1f909f1[m[33m ([m[1;31morigin/issue/271.1-create-new-backend-routes-connection[m[33m)[m HEAD@{30}: checkout: moving from issue/271.3-update-routes-new-backend to issue/271.1-create-new-backend-routes-connection +[33m9d10585[m[33m ([m[1;31morigin/issue/271.3-update-routes-new-backend[m[33m, [m[1;32missue/271.3-update-routes-new-backend[m[33m)[m HEAD@{31}: checkout: moving from issue/271.1-create-new-backend-routes-connection to issue/271.3-update-routes-new-backend +[33m1f909f1[m[33m ([m[1;31morigin/issue/271.1-create-new-backend-routes-connection[m[33m)[m HEAD@{32}: rebase (continue) (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33m1f909f1[m[33m ([m[1;31morigin/issue/271.1-create-new-backend-routes-connection[m[33m)[m HEAD@{33}: rebase (continue): rebase +[33mf8f0b33[m HEAD@{34}: rebase (continue) (pick): ADD persistent login +[33ma5ec1e3[m HEAD@{35}: rebase (continue): rebase +[33m4d8dd6c[m HEAD@{36}: rebase (pick): ok +[33mfca864c[m HEAD@{37}: rebase (pick): ADD zustand and new backend login +[33mb4f38f6[m[33m ([m[1;32mdevelop[m[33m)[m HEAD@{38}: rebase (start): checkout develop +[33m70c1272[m HEAD@{39}: checkout: moving from develop to issue/271.1-create-new-backend-routes-connection +[33mb4f38f6[m[33m ([m[1;32mdevelop[m[33m)[m HEAD@{40}: pull (finish): returning to refs/heads/develop +[33mb4f38f6[m[33m ([m[1;32mdevelop[m[33m)[m HEAD@{41}: pull (pick): ok +[33mf2ca3ec[m HEAD@{42}: pull (start): checkout f2ca3ec0437aca4415077207441905f7ad0ea0ff +[33m1e76287[m HEAD@{43}: checkout: moving from issue/271.1-create-new-backend-routes-connection to develop +[33m70c1272[m HEAD@{44}: pull (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33m70c1272[m HEAD@{45}: pull (pick): rebase +[33m13fde09[m HEAD@{46}: pull (pick): ADD persistent login +[33m47b021e[m HEAD@{47}: pull (start): checkout 47b021ee3e0538d8f8015c7b7a05ebfb761396ae +[33m8b6eff8[m HEAD@{48}: commit: rebase +[33mea6b9e7[m HEAD@{49}: rebase (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33mea6b9e7[m HEAD@{50}: rebase (pick): rebase +[33m021900e[m HEAD@{51}: rebase (pick): ok +[33m9d10585[m[33m ([m[1;31morigin/issue/271.3-update-routes-new-backend[m[33m, [m[1;32missue/271.3-update-routes-new-backend[m[33m)[m HEAD@{52}: rebase (start): checkout issue/271.3-update-routes-new-backend +[33m47b021e[m HEAD@{53}: checkout: moving from issue/271.3-update-routes-new-backend to issue/271.1-create-new-backend-routes-connection +[33m9d10585[m[33m ([m[1;31morigin/issue/271.3-update-routes-new-backend[m[33m, [m[1;32missue/271.3-update-routes-new-backend[m[33m)[m HEAD@{54}: pull: Fast-forward +[33md6ffb7b[m HEAD@{55}: checkout: moving from issue/271.1-create-new-backend-routes-connection to issue/271.3-update-routes-new-backend +[33m47b021e[m HEAD@{56}: commit: rebase +[33mc682d43[m HEAD@{57}: rebase (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33mc682d43[m HEAD@{58}: rebase (fixup): ok +[33m826a265[m HEAD@{59}: rebase (fixup): # This is a combination of 2 commits. +[33m411e37c[m HEAD@{60}: rebase (pick): ok +[33md6ffb7b[m HEAD@{61}: rebase (start): checkout issue/271.3-update-routes-new-backend +[33m05a1946[m HEAD@{62}: checkout: moving from issue/271.3-update-routes-new-backend to issue/271.1-create-new-backend-routes-connection +[33md6ffb7b[m HEAD@{63}: checkout: moving from issue/271.1-create-new-backend-routes-connection to issue/271.3-update-routes-new-backend +[33m05a1946[m HEAD@{64}: commit: ok +[33mdcfed71[m HEAD@{65}: rebase (finish): returning to refs/heads/issue/271.1-create-new-backend-routes-connection +[33mdcfed71[m HEAD@{66}: rebase (pick): routes-created-and-fixed +[33m1e76287[m HEAD@{67}: rebase (start): checkout develop +[33mb7fc31a[m HEAD@{68}: checkout: moving from develop to issue/271.1-create-new-backend-routes-connection +[33m1e76287[m HEAD@{69}: pull (finish): returning to refs/heads/develop +[33m1e76287[m HEAD@{70}: pull (pick): ok +[33m8823613[m HEAD@{71}: pull (start): checkout 882361329901314da9a514860097eec5c34fdcc7 +[33m4a496bd[m HEAD@{72}: checkout: moving from issue/271.1-create-new-backend-routes-connection to develop +[33mb7fc31a[m HEAD@{73}: commit: routes-created-and-fixed +[33m4a496bd[m HEAD@{74}: Branch: renamed refs/heads/issue/271.1-create-new-backend-routes-conection to refs/heads/issue/271.1-create-new-backend-routes-connection +[33m4a496bd[m HEAD@{76}: Branch: renamed refs/heads/issue/271.1-create-new-backend-routes-coneciton to refs/heads/issue/271.1-create-new-backend-routes-conection +[33m4a496bd[m HEAD@{78}: Branch: renamed refs/heads/issue/271.1-update-collection-routes to refs/heads/issue/271.1-create-new-backend-routes-coneciton +[33m4a496bd[m HEAD@{80}: Branch: renamed refs/heads/Issue/271.1 to refs/heads/issue/271.1-update-collection-routes +[33m4a496bd[m HEAD@{82}: checkout: moving from develop to Issue/271.1 +[33m4a496bd[m HEAD@{83}: rebase (continue) (finish): returning to refs/heads/develop +[33m4a496bd[m HEAD@{84}: rebase (continue): ok +[33mffa3940[m HEAD@{85}: pull (start): checkout ffa39400246a243d9f43f8d9071e4037b75e48af +[33m7d9f13f[m HEAD@{86}: rebase (continue) (finish): returning to refs/heads/develop +[33m7d9f13f[m HEAD@{87}: rebase (continue) (finish): refs/heads/develop onto ffa39400246a243d9f43f8d9071e4037b75e48af +[33m7d9f13f[m HEAD@{88}: checkout: moving from ffa39400246a243d9f43f8d9071e4037b75e48af to develop +[33mffa3940[m HEAD@{89}: pull (start): checkout ffa39400246a243d9f43f8d9071e4037b75e48af +[33m7d9f13f[m HEAD@{90}: checkout: moving from develop to develop +[33m7d9f13f[m HEAD@{91}: checkout: moving from arrumar to develop +[33meec953e[m[33m ([m[1;31morigin/arrumar[m[33m, [m[1;32marrumar[m[33m)[m HEAD@{92}: rebase (finish): returning to refs/heads/arrumar +[33meec953e[m[33m ([m[1;31morigin/arrumar[m[33m, [m[1;32marrumar[m[33m)[m HEAD@{93}: rebase (reword): Alteração feita +[33m591a2f3[m HEAD@{94}: rebase: fast-forward +[33m7d9f13f[m HEAD@{95}: rebase (start): checkout develop +[33m591a2f3[m HEAD@{96}: checkout: moving from develop to arrumar +[33m7d9f13f[m HEAD@{97}: checkout: moving from arrumar to develop +[33m591a2f3[m HEAD@{98}: commit: ok +[33m7d9f13f[m HEAD@{99}: checkout: moving from develop to arrumar +[33m7d9f13f[m HEAD@{100}: commit: ok +[33mb2ca474[m HEAD@{101}: pull: Fast-forward +[33mba98771[m HEAD@{102}: checkout: moving from issue/250-change-about-page to develop +[33m5c1abb3[m[33m ([m[1;31morigin/issue/250-change-about-page[m[33m, [m[1;32missue/250-change-about-page[m[33m)[m HEAD@{103}: rebase (finish): returning to refs/heads/issue/250-change-about-page +[33m5c1abb3[m[33m ([m[1;31morigin/issue/250-change-about-page[m[33m, [m[1;32missue/250-change-about-page[m[33m)[m HEAD@{104}: rebase (fixup): Issue #250/FIX-About-page +[33m0a72404[m HEAD@{105}: rebase (fixup): # This is a combination of 4 commits. +[33m51f2869[m HEAD@{106}: rebase (fixup): # This is a combination of 3 commits. +[33m09f1e1e[m HEAD@{107}: rebase (fixup): # This is a combination of 2 commits. +[33mbca4771[m HEAD@{108}: rebase (reword): Issue #250/FIX-About-page +[33mbc34395[m HEAD@{109}: rebase (reword): Issue #245/Credits-screen +[33mba98771[m HEAD@{110}: rebase (start): checkout develop +[33macaa2cf[m HEAD@{111}: checkout: moving from develop to issue/250-change-about-page +[33mba98771[m HEAD@{112}: checkout: moving from issue/250-change-about-page to develop +[33macaa2cf[m HEAD@{113}: commit: Done +[33m21d386b[m HEAD@{114}: checkout: moving from develop to issue/250-change-about-page +[33mba98771[m HEAD@{115}: checkout: moving from issue-247/update-header-text to develop +[33m9a5c0bd[m[33m ([m[1;31morigin/issue-247/update-header-text[m[33m, [m[1;32missue-247/update-header-text[m[33m)[m HEAD@{116}: rebase (finish): returning to refs/heads/issue-247/update-header-text +[33m9a5c0bd[m[33m ([m[1;31morigin/issue-247/update-header-text[m[33m, [m[1;32missue-247/update-header-text[m[33m)[m HEAD@{117}: rebase (pick): Issue #247: CHANGED header in mobile version +[33m5dfb808[m HEAD@{118}: rebase (pick): Issue #248: ADD Stats gamification +[33m7469b4e[m HEAD@{119}: rebase (pick): Issue #245/Credits-screen +[33mba98771[m HEAD@{120}: rebase (start): checkout develop +[33m2d4a62a[m HEAD@{121}: checkout: moving from develop to issue-247/update-header-text +[33mba98771[m HEAD@{122}: checkout: moving from fix to develop +[33m61220db[m[33m ([m[1;31morigin/fix[m[33m, [m[1;32mfix[m[33m)[m HEAD@{123}: rebase (finish): returning to refs/heads/fix +[33m61220db[m[33m ([m[1;31morigin/fix[m[33m, [m[1;32mfix[m[33m)[m HEAD@{124}: rebase (fixup): Issue #245/Credits-screen +[33m2091574[m HEAD@{125}: rebase (fixup): # This is a combination of 3 commits. +[33m2cc544c[m HEAD@{126}: rebase (fixup): # This is a combination of 2 commits. +[33m6837f38[m HEAD@{127}: rebase (pick): Issue #245/Credits-screen +[33mba98771[m HEAD@{128}: rebase (start): checkout develop +[33mf0702ca[m HEAD@{129}: rebase (abort): updating HEAD +[33m16d357c[m HEAD@{130}: rebase (pick): Issue #247: CHANGED header in mobile version +[33mc33ddee[m HEAD@{131}: rebase (pick): Issue #248: ADD Stats gamification +[33m500f180[m HEAD@{132}: rebase (pick): Issue #245/Credits-screen +[33mba98771[m HEAD@{133}: rebase (start): checkout develop +[33mf0702ca[m HEAD@{134}: checkout: moving from develop to fix +[33mba98771[m HEAD@{135}: pull (finish): returning to refs/heads/develop +[33mba98771[m HEAD@{136}: pull (start): checkout ba98771b62ede2cef14224f8bfb5a63f4d01d88b +[33m5d4bb5c[m HEAD@{137}: checkout: moving from fix to develop +[33mf0702ca[m HEAD@{138}: commit: HOTFIX: Commit history on dev + api ref +[33m5d4bb5c[m HEAD@{139}: checkout: moving from develop to fix +[33m5d4bb5c[m HEAD@{140}: checkout: moving from issue/250-change-about-page to develop +[33m21d386b[m HEAD@{141}: checkout: moving from develop to issue/250-change-about-page +[33m5d4bb5c[m HEAD@{142}: checkout: moving from issue/250-change-about-page to develop +[33m21d386b[m HEAD@{143}: commit: Issue #250: FIX About page +[33m5d4bb5c[m HEAD@{144}: checkout: moving from develop to issue/250-change-about-page +[33m5d4bb5c[m HEAD@{145}: checkout: moving from issue/250-change-about-page to develop +[33m5d4bb5c[m HEAD@{146}: checkout: moving from develop to issue/250-change-about-page +[33m5d4bb5c[m HEAD@{147}: pull: Fast-forward +[33mcfb4aab[m HEAD@{148}: checkout: moving from issue-247/update-header-text to develop +[33m2d4a62a[m HEAD@{149}: rebase (finish): returning to refs/heads/issue-247/update-header-text +[33m2d4a62a[m HEAD@{150}: rebase (fixup): Issue #247: CHANGED header in mobile version +[33m6391f47[m HEAD@{151}: rebase (pick): Issue #247: CHANGED header in mobile version +[33mcfb4aab[m HEAD@{152}: rebase (start): checkout develop +[33m1085e0e[m HEAD@{153}: checkout: moving from develop to issue-247/update-header-text +[33mcfb4aab[m HEAD@{154}: pull: Fast-forward +[33m131951b[m HEAD@{155}: checkout: moving from issue-247/update-header-text to develop +[33m1085e0e[m HEAD@{156}: commit: done +[33mb2d65e7[m HEAD@{157}: rebase (continue) (finish): returning to refs/heads/issue-247/update-header-text +[33mb2d65e7[m HEAD@{158}: rebase (continue) (fixup): Issue #247: CHANGED header in mobile version +[33me284390[m HEAD@{159}: rebase (continue): Issue #247: CHANGED header in mobile version +[33m131951b[m HEAD@{160}: rebase (start): checkout develop +[33m37fd1d3[m HEAD@{161}: checkout: moving from develop to issue-247/update-header-text +[33m131951b[m HEAD@{162}: pull: Fast-forward +[33m9170068[m HEAD@{163}: checkout: moving from issue-247/update-header-text to develop +[33m37fd1d3[m HEAD@{164}: checkout: moving from develop to issue-247/update-header-text +[33m9170068[m HEAD@{165}: checkout: moving from issue/245-credits-screen to develop +[33m427ebc3[m[33m ([m[1;31morigin/issue/245-credits-screen[m[33m, [m[1;32missue/245-credits-screen[m[33m)[m HEAD@{166}: rebase (finish): returning to refs/heads/issue/245-credits-screen +[33m427ebc3[m[33m ([m[1;31morigin/issue/245-credits-screen[m[33m, [m[1;32missue/245-credits-screen[m[33m)[m HEAD@{167}: rebase (fixup): Issue #245/Credits-screen +[33m3a5125b[m HEAD@{168}: rebase (fixup): # This is a combination of 2 commits. +[33m3610a07[m HEAD@{169}: rebase (reword): Issue #245/Credits-screen +[33m6b5d22e[m HEAD@{170}: rebase (reword): para nao perder +[33m9170068[m HEAD@{171}: rebase (start): checkout develop +[33m0e2d735[m HEAD@{172}: checkout: moving from develop to issue/245-credits-screen +[33m9170068[m HEAD@{173}: pull: Fast-forward +[33mb204e06[m HEAD@{174}: checkout: moving from issue/245-credits-screen to develop +[33m0e2d735[m HEAD@{175}: commit: done +[33m6eb0220[m HEAD@{176}: commit: responsive ok +[33m41c0687[m HEAD@{177}: commit: para nao perder +[33mb204e06[m HEAD@{178}: checkout: moving from develop to issue/245-credits-screen +[33mb204e06[m HEAD@{179}: pull: Fast-forward +[33m176ac2d[m HEAD@{180}: checkout: moving from issue/239-cards-divorce to develop +[33m96f81ab[m[33m ([m[1;31morigin/issue/239-cards-divorce[m[33m, [m[1;32missue/239-cards-divorce[m[33m)[m HEAD@{181}: rebase (finish): returning to refs/heads/issue/239-cards-divorce +[33m96f81ab[m[33m ([m[1;31morigin/issue/239-cards-divorce[m[33m, [m[1;32missue/239-cards-divorce[m[33m)[m HEAD@{182}: rebase (fixup): preciso rebasear +[33m5505157[m HEAD@{183}: rebase (start): checkout develop +[33mcf0671f[m HEAD@{184}: checkout: moving from develop to issue/239-cards-divorce +[33m176ac2d[m HEAD@{185}: checkout: moving from issue/239-cards-divorce to develop +[33mcf0671f[m HEAD@{186}: rebase (continue) (finish): returning to refs/heads/issue/239-cards-divorce +[33mcf0671f[m HEAD@{187}: rebase (continue): rebase +[33m5505157[m HEAD@{188}: rebase (continue): preciso rebasear +[33m176ac2d[m HEAD@{189}: rebase (start): checkout develop +[33meb4aee5[m HEAD@{190}: checkout: moving from develop to issue/239-cards-divorce +[33m176ac2d[m HEAD@{191}: pull: Fast-forward +[33m3b90816[m HEAD@{192}: checkout: moving from issue/239-cards-divorce to develop +[33meb4aee5[m HEAD@{193}: reset: moving to HEAD +[33meb4aee5[m HEAD@{194}: rebase (finish): returning to refs/heads/issue/239-cards-divorce +[33meb4aee5[m HEAD@{195}: rebase (start): checkout develop +[33meb4aee5[m HEAD@{196}: checkout: moving from develop to issue/239-cards-divorce +[33m3b90816[m HEAD@{197}: checkout: moving from issue/239-cards-divorce to develop +[33meb4aee5[m HEAD@{198}: commit: rebase +[33m464536b[m HEAD@{199}: rebase (continue) (finish): returning to refs/heads/issue/239-cards-divorce +[33m464536b[m HEAD@{200}: rebase (continue): preciso rebasear +[33m3b90816[m HEAD@{201}: rebase (start): checkout develop +[33m92b94ba[m HEAD@{202}: checkout: moving from develop to issue/239-cards-divorce +[33m3b90816[m HEAD@{203}: pull: Fast-forward +[33mc7dcecf[m HEAD@{204}: checkout: moving from issue/239-cards-divorce to develop +[33m92b94ba[m HEAD@{205}: commit: preciso rebasear +[33mc7dcecf[m HEAD@{206}: checkout: moving from develop to issue/239-cards-divorce +[33mc7dcecf[m HEAD@{207}: checkout: moving from issue/239-cards-divorce to develop +[33mc7dcecf[m HEAD@{208}: checkout: moving from develop to issue/239-cards-divorce +[33mc7dcecf[m HEAD@{209}: pull: Fast-forward +[33m1704eb1[m HEAD@{210}: checkout: moving from issue/225-aligment to develop +[33m7d799a2[m[33m ([m[1;31morigin/issue/225-aligment[m[33m, [m[1;32missue/225-aligment[m[33m)[m HEAD@{211}: rebase (finish): returning to refs/heads/issue/225-aligment +[33m7d799a2[m[33m ([m[1;31morigin/issue/225-aligment[m[33m, [m[1;32missue/225-aligment[m[33m)[m HEAD@{212}: rebase (reword): Issue #225/aligment +[33m2001295[m HEAD@{213}: rebase (reword): ok +[33m1704eb1[m HEAD@{214}: rebase (start): checkout develop +[33me61f0b8[m HEAD@{215}: checkout: moving from develop to issue/225-aligment +[33m1704eb1[m HEAD@{216}: pull: Fast-forward +[33m334adae[m HEAD@{217}: checkout: moving from issue/225-aligment to develop +[33me61f0b8[m HEAD@{218}: commit: ok +[33m334adae[m HEAD@{219}: checkout: moving from develop to issue/225-aligment +[33m334adae[m HEAD@{220}: checkout: moving from issue/225-alignment to develop +[33mbb96091[m[33m ([m[1;31morigin/issue/225-alignment[m[33m, [m[1;32missue/225-alignment[m[33m)[m HEAD@{221}: checkout: moving from develop to issue/225-alignment +[33m334adae[m HEAD@{222}: pull: Fast-forward +[33m02a4569[m HEAD@{223}: checkout: moving from issue/225-alignment to develop +[33mbb96091[m[33m ([m[1;31morigin/issue/225-alignment[m[33m, [m[1;32missue/225-alignment[m[33m)[m HEAD@{224}: rebase (finish): returning to refs/heads/issue/225-alignment +[33mbb96091[m[33m ([m[1;31morigin/issue/225-alignment[m[33m, [m[1;32missue/225-alignment[m[33m)[m HEAD@{225}: rebase (reword): Issue #225/Aligment +[33m6731cdc[m HEAD@{226}: rebase (reword): Feito +[33m02a4569[m HEAD@{227}: rebase (start): checkout develop +[33m988d099[m HEAD@{228}: checkout: moving from develop to issue/225-alignment +[33m02a4569[m HEAD@{229}: pull: Fast-forward +[33m26949de[m HEAD@{230}: checkout: moving from issue/225-alignment to develop +[33m988d099[m HEAD@{231}: commit: Feito +[33m1f3ba1c[m HEAD@{232}: rebase (finish): returning to refs/heads/issue/225-alignment +[33m1f3ba1c[m HEAD@{233}: rebase (fixup): Issue #225-Aligment +[33mbd0ce61[m HEAD@{234}: rebase (fixup): # This is a combination of 3 commits. +[33m54f9fcc[m HEAD@{235}: rebase (fixup): # This is a combination of 2 commits. +[33m9406554[m HEAD@{236}: rebase (reword): Issue #225-Aligment +[33m94bbc4d[m HEAD@{237}: rebase (reword): Mesclando +[33m26949de[m HEAD@{238}: rebase (start): checkout develop +[33meaeb503[m HEAD@{239}: checkout: moving from develop to issue/225-alignment +[33m26949de[m HEAD@{240}: checkout: moving from issue/225-alignment to develop +[33meaeb503[m HEAD@{241}: commit: finished +[33ma330880[m HEAD@{242}: pull (finish): returning to refs/heads/issue/225-alignment +[33ma330880[m HEAD@{243}: pull (start): checkout a330880eb58b3458db220419dd56cff7060e3c19 +[33mf75d033[m HEAD@{244}: checkout: moving from develop to issue/225-alignment +[33m26949de[m HEAD@{245}: pull: Fast-forward +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{246}: reset: moving to HEAD +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{247}: checkout: moving from issue/224-hotfix-resources to develop +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{248}: checkout: moving from develop to issue/224-hotfix-resources +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{249}: checkout: moving from HOTFIX-button to develop +[33m7a641e9[m[33m ([m[1;31morigin/HOTFIX-button[m[33m, [m[1;32mHOTFIX-button[m[33m)[m HEAD@{250}: rebase (finish): returning to refs/heads/HOTFIX-button +[33m7a641e9[m[33m ([m[1;31morigin/HOTFIX-button[m[33m, [m[1;32mHOTFIX-button[m[33m)[m HEAD@{251}: rebase (reword): Issue #223/ HOTFIX-button +[33m0f9a5c3[m HEAD@{252}: rebase: fast-forward +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{253}: rebase (start): checkout develop +[33m0f9a5c3[m HEAD@{254}: checkout: moving from develop to HOTFIX-button +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{255}: checkout: moving from HOTFIX-button to develop +[33m0f9a5c3[m HEAD@{256}: commit: button-fixed +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{257}: checkout: moving from develop to HOTFIX-button +[33m499d26b[m[33m ([m[1;32missue/224-hotfix-resources[m[33m)[m HEAD@{258}: pull: Fast-forward +[33m62ba0a5[m HEAD@{259}: checkout: moving from issue/225-alignment to develop +[33mf75d033[m HEAD@{260}: commit: ajuda +[33mcd4b0cb[m HEAD@{261}: commit: contato-page +[33m4447d1c[m HEAD@{262}: commit: publicar-page +[33mb983ba5[m HEAD@{263}: commit: sobre-page +[33md0f1447[m HEAD@{264}: checkout: moving from develop to issue/225-alignment +[33m62ba0a5[m HEAD@{265}: pull: Fast-forward +[33m90a6865[m HEAD@{266}: checkout: moving from issue/207-HOTFIX-complaint-tag to develop +[33m6edfdd7[m[33m ([m[1;31morigin/issue/207-HOTFIX-complaint-tag[m[33m, [m[1;32missue/207-HOTFIX-complaint-tag[m[33m)[m HEAD@{267}: rebase (finish): returning to refs/heads/issue/207-HOTFIX-complaint-tag +[33m6edfdd7[m[33m ([m[1;31morigin/issue/207-HOTFIX-complaint-tag[m[33m, [m[1;32missue/207-HOTFIX-complaint-tag[m[33m)[m HEAD@{268}: rebase (fixup): Issue #207/Complaints-homologation-page +[33m1bad2b0[m HEAD@{269}: rebase (fixup): # This is a combination of 2 commits. +[33mac0310b[m HEAD@{270}: rebase (start): checkout develop +[33m74e8f57[m HEAD@{271}: commit: done +[33m7880bde[m HEAD@{272}: checkout: moving from develop to issue/207-HOTFIX-complaint-tag +[33m90a6865[m HEAD@{273}: checkout: moving from issue/207-HOTFIX-complaint-tag to develop +[33m7880bde[m HEAD@{274}: commit: ok +[33mac0310b[m HEAD@{275}: rebase (continue) (finish): returning to refs/heads/issue/207-HOTFIX-complaint-tag +[33mac0310b[m HEAD@{276}: rebase (continue): Issue #207/Complaints-homologation-page +[33m90a6865[m HEAD@{277}: rebase (start): checkout develop +[33m2ec770c[m HEAD@{278}: checkout: moving from develop to issue/207-HOTFIX-complaint-tag +[33m90a6865[m HEAD@{279}: pull: Fast-forward +[33m82504e2[m HEAD@{280}: checkout: moving from issue/207-HOTFIX-complaint-tag to develop +[33m2ec770c[m HEAD@{281}: commit: ok +[33m82504e2[m HEAD@{282}: checkout: moving from develop to issue/207-HOTFIX-complaint-tag +[33m82504e2[m HEAD@{283}: pull: Fast-forward +[33m8171780[m HEAD@{284}: checkout: moving from issue/176-HOTFIX-resource-tag to develop +[33m1a29408[m[33m ([m[1;31morigin/issue/176-HOTFIX-resource-tag[m[33m, [m[1;32missue/176-HOTFIX-resource-tag[m[33m)[m HEAD@{285}: rebase (continue) (finish): returning to refs/heads/issue/176-HOTFIX-resource-tag +[33m1a29408[m[33m ([m[1;31morigin/issue/176-HOTFIX-resource-tag[m[33m, [m[1;32missue/176-HOTFIX-resource-tag[m[33m)[m HEAD@{286}: rebase (continue): ai +[33m8171780[m HEAD@{287}: rebase (start): checkout develop +[33m6bcba4e[m HEAD@{288}: checkout: moving from develop to issue/176-HOTFIX-resource-tag +[33m8171780[m HEAD@{289}: pull: Fast-forward +[33m5c37def[m HEAD@{290}: checkout: moving from issue/176-HOTFIX-resource-tag to develop +[33m6bcba4e[m HEAD@{291}: commit: ai +[33m5c37def[m HEAD@{292}: checkout: moving from develop to issue/176-HOTFIX-resource-tag +[33m5c37def[m HEAD@{293}: checkout: moving from issue/175-HOTFIX to develop +[33m34cf5fb[m[33m ([m[1;31morigin/issue/175-HOTFIX[m[33m, [m[1;32missue/175-HOTFIX[m[33m)[m HEAD@{294}: rebase (finish): returning to refs/heads/issue/175-HOTFIX +[33m34cf5fb[m[33m ([m[1;31morigin/issue/175-HOTFIX[m[33m, [m[1;32missue/175-HOTFIX[m[33m)[m HEAD@{295}: rebase (reword): Issue #175/HOTFIX +[33mcda84b7[m HEAD@{296}: rebase (reword): done +[33m5c37def[m HEAD@{297}: rebase (start): checkout develop +[33m456859e[m HEAD@{298}: checkout: moving from develop to issue/175-HOTFIX +[33m5c37def[m HEAD@{299}: pull: Fast-forward +[33m3a58570[m HEAD@{300}: checkout: moving from issue/175-HOTFIX to develop +[33m456859e[m HEAD@{301}: commit: done +[33m3a58570[m HEAD@{302}: checkout: moving from develop to issue/175-HOTFIX +[33m3a58570[m HEAD@{303}: checkout: moving from issues/200/190-HOTFIX to develop +[33m61d06b0[m[33m ([m[1;31morigin/issues/200/190-HOTFIX[m[33m, [m[1;32missues/200/190-HOTFIX[m[33m)[m HEAD@{304}: rebase (finish): returning to refs/heads/issues/200/190-HOTFIX +[33m61d06b0[m[33m ([m[1;31morigin/issues/200/190-HOTFIX[m[33m, [m[1;32missues/200/190-HOTFIX[m[33m)[m HEAD@{305}: rebase (pick): Issue #190/#200-HOTFIX +[33m3a58570[m HEAD@{306}: rebase (start): checkout develop +[33m7499204[m HEAD@{307}: checkout: moving from develop to issues/200/190-HOTFIX +[33m3a58570[m HEAD@{308}: pull: Fast-forward +[33m2136eb2[m HEAD@{309}: checkout: moving from issues/200/190-HOTFIX to develop +[33m7499204[m HEAD@{310}: rebase (finish): returning to refs/heads/issues/200/190-HOTFIX +[33m7499204[m HEAD@{311}: rebase (reword): Issue #190/#200-HOTFIX +[33m14073c5[m HEAD@{312}: rebase (reword): done +[33m2136eb2[m HEAD@{313}: rebase (start): checkout develop +[33mbef8e1e[m HEAD@{314}: checkout: moving from develop to issues/200/190-HOTFIX +[33m2136eb2[m HEAD@{315}: pull: Fast-forward +[33m4fa1ef5[m HEAD@{316}: checkout: moving from issues/200/190-HOTFIX to develop +[33mbef8e1e[m HEAD@{317}: commit: done +[33m4fa1ef5[m HEAD@{318}: rebase (finish): returning to refs/heads/issues/200/190-HOTFIX +[33m4fa1ef5[m HEAD@{319}: rebase (start): checkout develop +[33md63b0d0[m HEAD@{320}: reset: moving to HEAD +[33md63b0d0[m HEAD@{321}: checkout: moving from develop to issues/200/190-HOTFIX +[33m4fa1ef5[m HEAD@{322}: pull: Fast-forward +[33md63b0d0[m HEAD@{323}: checkout: moving from issues/200/190-HOTFIX to develop +[33md63b0d0[m HEAD@{324}: checkout: moving from develop to issues/200/190-HOTFIX +[33md63b0d0[m HEAD@{325}: checkout: moving from issue/193/194-HOTFIX to develop +[33md7dbfc0[m[33m ([m[1;31morigin/issue/193/194-HOTFIX[m[33m, [m[1;32missue/193/194-HOTFIX[m[33m)[m HEAD@{326}: rebase (finish): returning to refs/heads/issue/193/194-HOTFIX +[33md7dbfc0[m[33m ([m[1;31morigin/issue/193/194-HOTFIX[m[33m, [m[1;32missue/193/194-HOTFIX[m[33m)[m HEAD@{327}: rebase (reword): Issue #193#104-HOTFIX +[33m335aeac[m HEAD@{328}: rebase (reword): done +[33md63b0d0[m HEAD@{329}: rebase (start): checkout develop +[33m9d04c2d[m HEAD@{330}: checkout: moving from develop to issue/193/194-HOTFIX +[33md63b0d0[m HEAD@{331}: pull: Fast-forward +[33m32299fc[m HEAD@{332}: checkout: moving from issue/193/194-HOTFIX to develop +[33m9d04c2d[m HEAD@{333}: commit: done +[33mf8e4018[m[33m ([m[1;31morigin/issue/203/198/HOTFIX[m[33m, [m[1;32missue/203/198/HOTFIX[m[33m)[m HEAD@{334}: checkout: moving from issue/203/198/HOTFIX to issue/193/194-HOTFIX +[33mf8e4018[m[33m ([m[1;31morigin/issue/203/198/HOTFIX[m[33m, [m[1;32missue/203/198/HOTFIX[m[33m)[m HEAD@{335}: rebase (finish): returning to refs/heads/issue/203/198/HOTFIX +[33mf8e4018[m[33m ([m[1;31morigin/issue/203/198/HOTFIX[m[33m, [m[1;32missue/203/198/HOTFIX[m[33m)[m HEAD@{336}: rebase (reword): Issue #198#203/HOTFIX +[33md9a1b37[m HEAD@{337}: rebase (reword): done +[33m32299fc[m HEAD@{338}: rebase (start): checkout develop +[33ma166174[m HEAD@{339}: checkout: moving from develop to issue/203/198/HOTFIX +[33m32299fc[m HEAD@{340}: pull: Fast-forward +[33m61308b7[m HEAD@{341}: checkout: moving from issue/203/198/HOTFIX to develop +[33ma166174[m HEAD@{342}: commit: done +[33m61308b7[m HEAD@{343}: checkout: moving from develop to issue/203/198/HOTFIX +[33m61308b7[m HEAD@{344}: pull: Fast-forward +[33mae927a6[m HEAD@{345}: checkout: moving from issue/196-HOTFIX-collection-modal to develop +[33m63abe42[m[33m ([m[1;31morigin/issue/196-HOTFIX-collection-modal[m[33m, [m[1;32missue/196-HOTFIX-collection-modal[m[33m)[m HEAD@{346}: rebase (finish): returning to refs/heads/issue/196-HOTFIX-collection-modal +[33m63abe42[m[33m ([m[1;31morigin/issue/196-HOTFIX-collection-modal[m[33m, [m[1;32missue/196-HOTFIX-collection-modal[m[33m)[m HEAD@{347}: rebase (fixup): Issue #196#198/HOTFIX +[33m86affb9[m HEAD@{348}: rebase (start): checkout develop +[33m1b2ecb6[m HEAD@{349}: commit: done1 +[33m86affb9[m HEAD@{350}: rebase (finish): returning to refs/heads/issue/196-HOTFIX-collection-modal +[33m86affb9[m HEAD@{351}: rebase (fixup): Issue #196#198/HOTFIX +[33m0eb8e89[m HEAD@{352}: rebase (reword): Issue #196#198/HOTFIX +[33m76ba4e1[m HEAD@{353}: rebase (reword): done +[33mae927a6[m HEAD@{354}: rebase (start): checkout develop +[33mbbb59ff[m HEAD@{355}: checkout: moving from develop to issue/196-HOTFIX-collection-modal +[33mae927a6[m HEAD@{356}: pull: Fast-forward +[33m919173f[m HEAD@{357}: checkout: moving from issue/196-HOTFIX-collection-modal to develop +[33mbbb59ff[m HEAD@{358}: commit: done +[33mb2fa5c7[m HEAD@{359}: commit: done +[33m919173f[m HEAD@{360}: checkout: moving from develop to issue/196-HOTFIX-collection-modal +[33m919173f[m HEAD@{361}: checkout: moving from issue/183-HOTFIX-carousel to develop +[33me326144[m[33m ([m[1;31morigin/issue/183-HOTFIX-carousel[m[33m, [m[1;32missue/183-HOTFIX-carousel[m[33m)[m HEAD@{362}: rebase (finish): returning to refs/heads/issue/183-HOTFIX-carousel +[33me326144[m[33m ([m[1;31morigin/issue/183-HOTFIX-carousel[m[33m, [m[1;32missue/183-HOTFIX-carousel[m[33m)[m HEAD@{363}: rebase (pick): Issue #183/HOTFIX-about-page +[33m919173f[m HEAD@{364}: rebase (start): checkout develop +[33m7a91ab3[m HEAD@{365}: checkout: moving from develop to issue/183-HOTFIX-carousel +[33m919173f[m HEAD@{366}: pull: Fast-forward +[33mb27a638[m HEAD@{367}: checkout: moving from issue/183-HOTFIX-carousel to develop +[33m7a91ab3[m HEAD@{368}: rebase (finish): returning to refs/heads/issue/183-HOTFIX-carousel +[33m7a91ab3[m HEAD@{369}: rebase (fixup): Issue #183/HOTFIX-about-page +[33m65ff23c[m HEAD@{370}: rebase (fixup): # This is a combination of 2 commits. +[33m0464120[m HEAD@{371}: rebase (reword): Issue #183/HOTFIX-about-page +[33mbe9c3ad[m HEAD@{372}: rebase: fast-forward +[33mb27a638[m HEAD@{373}: rebase (start): checkout develop +[33mfbb4811[m HEAD@{374}: checkout: moving from develop to issue/183-HOTFIX-carousel +[33mb27a638[m HEAD@{375}: checkout: moving from issue/183-HOTFIX-carousel to develop +[33mfbb4811[m HEAD@{376}: commit: done +[33m59c4800[m HEAD@{377}: commit: done +[33mbe9c3ad[m HEAD@{378}: rebase (finish): returning to refs/heads/issue/183-HOTFIX-carousel +[33mbe9c3ad[m HEAD@{379}: rebase (reword): Issue #183/HOTFIX-carousel +[33m5e246a6[m HEAD@{380}: rebase: fast-forward +[33mb27a638[m HEAD@{381}: rebase (start): checkout develop +[33m5e246a6[m HEAD@{382}: checkout: moving from develop to issue/183-HOTFIX-carousel +[33mb27a638[m HEAD@{383}: checkout: moving from issue/183-HOTFIX-carousel to develop +[33m5e246a6[m HEAD@{384}: commit: done +[33mb27a638[m HEAD@{385}: checkout: moving from develop to issue/183-HOTFIX-carousel +[33mb27a638[m HEAD@{386}: pull: Fast-forward +[33mbb20958[m[33m ([m[1;32missue/169-HOTFIX-mobile-perfil[m[33m)[m HEAD@{387}: checkout: moving from issue/171-HOTFIX-collections-mobile to develop +[33m5da9975[m[33m ([m[1;31morigin/issue/171-HOTFIX-collections-mobile[m[33m, [m[1;32missue/171-HOTFIX-collections-mobile[m[33m)[m HEAD@{388}: commit: done +[33mbb20958[m[33m ([m[1;32missue/169-HOTFIX-mobile-perfil[m[33m)[m HEAD@{389}: checkout: moving from develop to issue/171-HOTFIX-collections-mobile +[33mbb20958[m[33m ([m[1;32missue/169-HOTFIX-mobile-perfil[m[33m)[m HEAD@{390}: checkout: moving from issue/169-HOTFIX-mobile-perfil to develop +[33mbb20958[m[33m ([m[1;32missue/169-HOTFIX-mobile-perfil[m[33m)[m HEAD@{391}: checkout: moving from develop to issue/169-HOTFIX-mobile-perfil +[33mbb20958[m[33m ([m[1;32missue/169-HOTFIX-mobile-perfil[m[33m)[m HEAD@{392}: pull: Fast-forward +[33m8c1d35a[m HEAD@{393}: checkout: moving from issue/166-HOTFIX-delete-collection to develop +[33mb7c531d[m[33m ([m[1;31morigin/issue/166-HOTFIX-delete-collection[m[33m, [m[1;32missue/166-HOTFIX-delete-collection[m[33m)[m HEAD@{394}: rebase (finish): returning to refs/heads/issue/166-HOTFIX-delete-collection +[33mb7c531d[m[33m ([m[1;31morigin/issue/166-HOTFIX-delete-collection[m[33m, [m[1;32missue/166-HOTFIX-delete-collection[m[33m)[m HEAD@{395}: rebase (reword): Issue #166:HOTFIX delete collection +[33m974e9c7[m HEAD@{396}: rebase: fast-forward +[33m8c1d35a[m HEAD@{397}: rebase (start): checkout develop +[33m974e9c7[m HEAD@{398}: checkout: moving from develop to issue/166-HOTFIX-delete-collection +[33m8c1d35a[m HEAD@{399}: checkout: moving from issue/166-HOTFIX-delete-collection to develop +[33m974e9c7[m HEAD@{400}: commit: done +[33m8c1d35a[m HEAD@{401}: rebase (finish): returning to refs/heads/issue/166-HOTFIX-delete-collection +[33m8c1d35a[m HEAD@{402}: rebase (start): checkout develop +[33m711b35d[m[33m ([m[1;31morigin/issue/162-HOTFIX-recurso[m[33m, [m[1;32missue/162-HOTFIX-recurso[m[33m)[m HEAD@{403}: reset: moving to HEAD +[33m711b35d[m[33m ([m[1;31morigin/issue/162-HOTFIX-recurso[m[33m, [m[1;32missue/162-HOTFIX-recurso[m[33m)[m HEAD@{404}: checkout: moving from develop to issue/166-HOTFIX-delete-collection +[33m8c1d35a[m HEAD@{405}: pull: Fast-forward +[33m711482c[m HEAD@{406}: checkout: moving from issue/166-HOTFIX-delete-collection to develop +[33m711b35d[m[33m ([m[1;31morigin/issue/162-HOTFIX-recurso[m[33m, [m[1;32missue/162-HOTFIX-recurso[m[33m)[m HEAD@{407}: checkout: moving from issue/162-HOTFIX-recurso to issue/166-HOTFIX-delete-collection +[33m711b35d[m[33m ([m[1;31morigin/issue/162-HOTFIX-recurso[m[33m, [m[1;32missue/162-HOTFIX-recurso[m[33m)[m HEAD@{408}: rebase (finish): returning to refs/heads/issue/162-HOTFIX-recurso +[33m711b35d[m[33m ([m[1;31morigin/issue/162-HOTFIX-recurso[m[33m, [m[1;32missue/162-HOTFIX-recurso[m[33m)[m HEAD@{409}: rebase (pick): Issue #162: HOTFIX resource +[33m711482c[m HEAD@{410}: rebase (start): checkout develop +[33mb856b1f[m HEAD@{411}: checkout: moving from develop to issue/162-HOTFIX-recurso +[33m711482c[m HEAD@{412}: checkout: moving from issue/162-HOTFIX-recurso to develop +[33mb856b1f[m HEAD@{413}: checkout: moving from develop to issue/162-HOTFIX-recurso +[33m711482c[m HEAD@{414}: pull: Fast-forward +[33m2c17be0[m HEAD@{415}: checkout: moving from develop to develop +[33m2c17be0[m HEAD@{416}: checkout: moving from issue/162-HOTFIX-recurso to develop +[33mb856b1f[m HEAD@{417}: rebase (finish): returning to refs/heads/issue/162-HOTFIX-recurso +[33mb856b1f[m HEAD@{418}: rebase (reword): Issue #162: HOTFIX resource +[33m4ad386b[m HEAD@{419}: rebase (reword): done +[33m2c17be0[m HEAD@{420}: rebase (start): checkout develop +[33m9898a07[m HEAD@{421}: checkout: moving from develop to issue/162-HOTFIX-recurso +[33m2c17be0[m HEAD@{422}: pull: Fast-forward +[33mf4c435e[m HEAD@{423}: checkout: moving from issue/162-HOTFIX-recurso to develop +[33m9898a07[m HEAD@{424}: commit: done +[33mf4c435e[m HEAD@{425}: checkout: moving from develop to issue/162-HOTFIX-recurso +[33mf4c435e[m HEAD@{426}: checkout: moving from issue/165-HOTFIX-home to develop +[33mb327101[m[33m ([m[1;31morigin/issue/165-HOTFIX-home[m[33m, [m[1;32missue/165-HOTFIX-home[m[33m)[m HEAD@{427}: rebase (finish): returning to refs/heads/issue/165-HOTFIX-home +[33mb327101[m[33m ([m[1;31morigin/issue/165-HOTFIX-home[m[33m, [m[1;32missue/165-HOTFIX-home[m[33m)[m HEAD@{428}: rebase (reword): Issue #165:HOTFIX home +[33m0e77362[m HEAD@{429}: rebase (reword): done +[33mf4c435e[m HEAD@{430}: rebase (start): checkout develop +[33m07645b3[m HEAD@{431}: checkout: moving from develop to issue/165-HOTFIX-home +[33mf4c435e[m HEAD@{432}: pull: Fast-forward +[33m58cf2a7[m HEAD@{433}: checkout: moving from issue/165-HOTFIX-home to develop +[33m07645b3[m HEAD@{434}: commit: done +[33m58cf2a7[m HEAD@{435}: checkout: moving from issue-164/Hotfix-colecao to issue/165-HOTFIX-home +[33m2c5f58a[m[33m ([m[1;31morigin/issue-164/Hotfix-colecao[m[33m, [m[1;32missue-164/Hotfix-colecao[m[33m)[m HEAD@{436}: checkout: moving from issue/165-HOTFIX-home to issue-164/Hotfix-colecao +[33m58cf2a7[m HEAD@{437}: checkout: moving from develop to issue/165-HOTFIX-home +[33m58cf2a7[m HEAD@{438}: checkout: moving from issue-164/Hotfix-colecao to develop +[33m2c5f58a[m[33m ([m[1;31morigin/issue-164/Hotfix-colecao[m[33m, [m[1;32missue-164/Hotfix-colecao[m[33m)[m HEAD@{439}: rebase (finish): returning to refs/heads/issue-164/Hotfix-colecao +[33m2c5f58a[m[33m ([m[1;31morigin/issue-164/Hotfix-colecao[m[33m, [m[1;32missue-164/Hotfix-colecao[m[33m)[m HEAD@{440}: rebase (reword): Issue #164/HOTFIX collection +[33me276d83[m HEAD@{441}: rebase (reword): done +[33m58cf2a7[m HEAD@{442}: rebase (start): checkout develop +[33m2a97aaa[m HEAD@{443}: checkout: moving from develop to issue-164/Hotfix-colecao +[33m58cf2a7[m HEAD@{444}: pull: Fast-forward +[33mb06cea0[m HEAD@{445}: checkout: moving from issue-164/Hotfix-colecao to develop +[33m2a97aaa[m HEAD@{446}: commit: done +[33mb06cea0[m HEAD@{447}: checkout: moving from develop to issue-164/Hotfix-colecao +[33mb06cea0[m HEAD@{448}: checkout: moving from issue-164/Hotfix-colecao to develop +[33mb06cea0[m HEAD@{449}: checkout: moving from develop to issue-164/Hotfix-colecao +[33mb06cea0[m HEAD@{450}: pull: Fast-forward +[33mfd5247e[m HEAD@{451}: checkout: moving from issue/158-fix-about-page to develop +[33mad77385[m[33m ([m[1;31morigin/issue/158-fix-about-page[m[33m, [m[1;32missue/158-fix-about-page[m[33m)[m HEAD@{452}: rebase (finish): returning to refs/heads/issue/158-fix-about-page +[33mad77385[m[33m ([m[1;31morigin/issue/158-fix-about-page[m[33m, [m[1;32missue/158-fix-about-page[m[33m)[m HEAD@{453}: rebase (pick): Issue #158/FIX about page +[33mfd5247e[m HEAD@{454}: rebase (start): checkout develop +[33me85b8e4[m HEAD@{455}: checkout: moving from develop to issue/158-fix-about-page +[33mfd5247e[m HEAD@{456}: pull: Fast-forward +[33m0762558[m HEAD@{457}: checkout: moving from issue/158-fix-about-page to develop +[33me85b8e4[m HEAD@{458}: rebase (finish): returning to refs/heads/issue/158-fix-about-page +[33me85b8e4[m HEAD@{459}: rebase (fixup): Issue #158/FIX about page +[33m9a55a4b[m HEAD@{460}: rebase (reword): Issue #158/FIX about page +[33m9eb9070[m HEAD@{461}: rebase: fast-forward +[33m0762558[m HEAD@{462}: rebase (start): checkout develop +[33mb91a62f[m HEAD@{463}: checkout: moving from develop to issue/158-fix-about-page +[33m0762558[m HEAD@{464}: checkout: moving from issue/158-fix-about-page to develop +[33mb91a62f[m HEAD@{465}: commit: fixed +[33m9eb9070[m HEAD@{466}: rebase (finish): returning to refs/heads/issue/158-fix-about-page +[33m9eb9070[m HEAD@{467}: rebase (reword): fixed +[33m55ec9be[m HEAD@{468}: rebase: fast-forward +[33m0762558[m HEAD@{469}: rebase (start): checkout develop +[33m55ec9be[m HEAD@{470}: checkout: moving from develop to issue/158-fix-about-page +[33m0762558[m HEAD@{471}: checkout: moving from issue/158-fix-about-page to develop +[33m55ec9be[m HEAD@{472}: commit: fixed +[33m0762558[m HEAD@{473}: checkout: moving from develop to issue/158-fix-about-page +[33m0762558[m HEAD@{474}: pull: Fast-forward +[33m8c42da0[m HEAD@{475}: pull: Fast-forward +[33mca14f32[m HEAD@{476}: checkout: moving from fix-detail to develop +[33m635ecb2[m[33m ([m[1;31morigin/fix-detail[m[33m, [m[1;32mfix-detail[m[33m)[m HEAD@{477}: rebase (finish): returning to refs/heads/fix-detail +[33m635ecb2[m[33m ([m[1;31morigin/fix-detail[m[33m, [m[1;32mfix-detail[m[33m)[m HEAD@{478}: rebase (reword): Simple change (no issue) +[33m948e1ee[m HEAD@{479}: rebase: fast-forward +[33mca14f32[m HEAD@{480}: rebase (start): checkout develop +[33m948e1ee[m HEAD@{481}: checkout: moving from develop to fix-detail +[33mca14f32[m HEAD@{482}: checkout: moving from fix-detail to develop +[33m948e1ee[m HEAD@{483}: commit: detail fixed +[33mca14f32[m HEAD@{484}: checkout: moving from develop to fix-detail +[33mca14f32[m HEAD@{485}: pull: Fast-forward +[33mc64803f[m HEAD@{486}: checkout: moving from issue/144-about-page-changes to develop +[33m004ea21[m[33m ([m[1;31morigin/issue/144-about-page-changes[m[33m, [m[1;32missue/144-about-page-changes[m[33m)[m HEAD@{487}: checkout: moving from develop to issue/144-about-page-changes +[33mc64803f[m HEAD@{488}: clone: from gitlab.c3sl.ufpr.br:mecred/frontend-mecred.git diff --git a/next.config.mjs b/next.config.mjs index 950d26acffb7e086f72c1635aebcd093ea30f05b..1c3824f5eed1a0c01605f125d68c34d79f1971c0 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -30,7 +30,7 @@ const nextConfig = { remotePatterns: [ { protocol: 'https', - hostname: 'api.mecred.c3sl.ufpr.br', + hostname: 's3.c3sl.ufpr.br', port: '', pathname: '/**', }, @@ -40,6 +40,12 @@ const nextConfig = { port: '443', pathname: '/**', }, + { + protocol: 'https', + hostname: 's3.c3sl.ufpr.br', + port: '', + pathname: '/**', + }, ], }, }; diff --git a/package.json b/package.json index e70348431b4ae2249972f91ad8facc94b7f7c4eb..31050032f5171e290f457f9bb300b9f23dec10cd 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", - "tailwind-scrollbar": "^3.1.0" + "tailwind-scrollbar": "^3.1.0", }, "devDependencies": { "@svgr/webpack": "^8.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 75a939b2c307e7eefe423979cf0063fc32240b2a..2557b4284c4813c8baa475c5a14f7abab9e49a5c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -65,6 +65,9 @@ importers: tailwind-scrollbar: specifier: ^3.1.0 version: 3.1.0(tailwindcss@3.4.10) + zustand: + specifier: ^5.0.3 + version: 5.0.3(@types/react@18.3.3)(react@18.3.1) devDependencies: '@svgr/webpack': specifier: ^8.1.0 diff --git a/script.py b/script.py new file mode 100644 index 0000000000000000000000000000000000000000..fbeea938d967c3290bf2c32282d47cf4898017f6 --- /dev/null +++ b/script.py @@ -0,0 +1,67 @@ +import requests + +# url da rota +url = 'http://localhost:3000/api/resource/create' + +# seu token jwt +token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwiZW1haWwiOiJhZG1pbkBhZG1pbi5jb20ifQ.HkGzCxkCCIKOmDy3FZ7enfaKQUQdAr4QudcBGJtZAEg' + +# json de entrada conforme o esperado pelo backend +data = { + "name": "recurso do thomas ", + "description": "descrição do recurso", + "author": "autorrrr", + "link": "www.gov.br", + "subjects": [1, 2], # ids das disciplinas + "language": [1], # ids dos idiomas + "educational_stages": [1, 2], # ids dos estágios educacionais + "license_id": 1, + "state": "accepted", + "object_type_id": 1, +} + +# cabeçalhos, incluindo o token de autorização +headers = { + 'Authorization': f'Bearer {token}', + 'Content-Type': 'application/json' +} + +original_title = data["name"] + +# faz a requisição post +for i in range(500): + data["name"] = f"{original_title} {i}" + response = requests.post( + url, + json=data, + headers=headers) + + # imprime o resultado + print(response) + print('status:', response.status_code) + print('resposta:', response.json()) + + + # 2. se deu certo, pega o id do recurso + if response.status_code == 200: + resource_id = response.json().get('id') # ajusta aqui conforme a chave real da resposta + + # 3. faz o upload da thumbnail + url_upload = 'http://localhost:3000/api/s3/upload/thumbnail/resource' + headers_upload = { + 'Authorization': f'Bearer {token}', + } + + files = { + 'file': ('thumbnail.png', open('thumbnail.png', 'rb'), 'image/png'), + 'id_resource': (None, str(resource_id)), + 'content_type': (None, 'image/png'), + } + + upload_response = requests.post(url_upload, files=files, headers=headers_upload) + print('upload status:', upload_response.status_code) + print('upload resposta:', upload_response.json()) + else: + print('erro ao criar recurso') + + print(i) \ No newline at end of file diff --git a/src/app/biblioteca/page.js b/src/app/biblioteca/page.js new file mode 100644 index 0000000000000000000000000000000000000000..85a4076429db245cf6c1789108c0190fb79c7913 --- /dev/null +++ b/src/app/biblioteca/page.js @@ -0,0 +1,34 @@ + +import { Suspense } from "react"; +import Loading from "../components/Loading"; +import Content from "../components/Content"; + + + +function tradutor(name) { + switch (name) { + case "resources": + return "Recursos" + case "collections": + return "Coleções" + case "MEC": + return "MEC" + case "users": + return "Usuários" + default: + return + } +} + +/** + * @param {Object} props + * @param {string} props.inputFilter +*/ +export default function Library({ searchParams }) { + return ( + <Suspense fallback={<Loading />}> + <Content searchParams={searchParams}/> + </Suspense> + ) + +} \ No newline at end of file diff --git a/src/app/busca/page.js b/src/app/busca/page.js index 896f7b1983a7a727cec572928aa1b7c0aac279db..432a56af9d59d91cdcb953d2499a15ff6743007f 100644 --- a/src/app/busca/page.js +++ b/src/app/busca/page.js @@ -9,13 +9,13 @@ import Content from "../components/Content"; function tradutor(name) { switch (name) { - case "LearningObject": + case "resources": return "Recursos" - case "Collection": + case "collections": return "Coleções" case "MEC": return "MEC" - case "User": + case "users": return "Usuários" default: return @@ -24,7 +24,6 @@ function tradutor(name) { /** * @param {Object} props - * @param {string} props.name informna se é recursos, coleções ou MEC * @param {string} props.inputFilter */ export default function Busca() { diff --git a/src/app/colecao/[id]/components/collectionItems.js b/src/app/colecao/[id]/components/collectionItems.js index 9cdadf17844f3add2ab6df8bf695601eb9c23434..ba20d57e755983ddc308075a6cb04a931799442d 100644 --- a/src/app/colecao/[id]/components/collectionItems.js +++ b/src/app/colecao/[id]/components/collectionItems.js @@ -1,27 +1,27 @@ import { useMediaQuery } from "@mui/material"; import Cards from "@/app/components/Cards"; -export default function CollectionItems({ collection }) { +export default function CollectionItems({ collection, resources }) { const isSm = useMediaQuery((theme) => theme.breakpoints.down('sm')); return ( <div className="flex flex-col bg-white-HC-dark p-4 rounded-lg"> <div className="text-darkGray-HC-white-underline font-bold text-xl mb-3">Recursos na coleção</div> <div className="flex flex-col gap-3"> - {collection.collection_items.sort((a,b) => a.position - b.position).map((item, index) => { + {resources?.sort((a,b) => a.position - b.position).map((item, index) => { return ( <Cards collectionSource={collection["id"]} horizontal={!isSm} noAvatar={!isSm} - id={item["collectionable"]["id"]} + id={item.id} key={index} - title={item["collectionable"]["name"]} - author={item["collectionable"]["publisher"]["name"]} - avatar={item["collectionable"]["publisher"]["avatar"]} - image={item["collectionable"]["thumbnail"]} - type={item["collectionable"]["object_type"]} - updated_at={item["collectionable"]["updated_at"]} + title={item.name} + author={item.author} + avatar={`https://s3.c3sl.ufpr.br/mecredteste/avatar/${collection.user_id}`} + image={`https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${item.id}`} + type={item.type} + updated_at={item.updated_at} thumbWidth={isSm ? "100%" : "230px"} thumbHeight="auto" width="100%" diff --git a/src/app/colecao/[id]/components/publisherInfoCollection.js b/src/app/colecao/[id]/components/publisherInfoCollection.js index e90319667cfaed7f0fb872bed7b6f4b6aff9dc3a..826150ab3e58677ea57ae66c6279ca061062789b 100644 --- a/src/app/colecao/[id]/components/publisherInfoCollection.js +++ b/src/app/colecao/[id]/components/publisherInfoCollection.js @@ -44,7 +44,7 @@ export default function PublisherInfoCollection({ publisher, disabledButton = fa const followHandler = () => { loginBarrier(); mecredApi.put( - `users/${publisher.id}/follow/`, + `public/users/follow/${publisher.id}`, {}, { headers: { diff --git a/src/app/colecao/[id]/page.js b/src/app/colecao/[id]/page.js index 47f00e45c8119b2d80d9ce39528bada1a7ea63c1..756393fe626e39de19a13ce8b6c142af9ffb0855 100644 --- a/src/app/colecao/[id]/page.js +++ b/src/app/colecao/[id]/page.js @@ -19,41 +19,31 @@ export default function Colecao({ params }) { const [needLoginOpen, setNeedLoginOpen] = useState(false); const [error, setError] = useState(false); const [isSmallScreen, setIsSmallScreen] = useState(false); - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); useEffect(() => { const fetchData = async () => { try { - let headers = {}; + const collectionResponse = await mecredApi.get(`public/collections/${params.id}`); + const collection = collectionResponse.data; + const resourcesResponse = await mecredApi.get(`public/collections/${params.id}/resources`); + const resources = resourcesResponse.data; + const ownerResponse = await mecredApi.get(`public/userCollections/${params.id}/owner`) + const owner = ownerResponse.data; - if (isLoggedIn()) { - headers = { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }; - } - - const response = await mecredApi.get(`collections/${params.id}`, {headers}); - setCollection(response.data); + setCollection({ ...collection, resources: resources, owner: owner }); } catch (error) { + console.error("Erro ao buscar dados:", error); setError(true); } }; - fetchData(); - }, [params.id, client, token, uid]); + }, [params.id]); useEffect(() => { // Função para checar o tamanho da tela const checkScreenSize = () => { setIsSmallScreen(window.innerWidth < 1280); }; - checkScreenSize(); // Checa no primeiro render window.addEventListener("resize", checkScreenSize); // Adiciona o listener @@ -76,33 +66,33 @@ export default function Colecao({ params }) { <div className="bg-ice-HC-dark p-3 w-full"> <div className=" flex justify-center"> {/* Pré-visualização */} - <CollectionPreview collection={collection} /> + <CollectionPreview collection={collection.collection} resources={collection?.resources.resources} /> </div> <div> <div> <div className="text-darkGray-HC-white mt-5 text-2xl font-bold"> {/* Título */} - <h1>{collection.name}</h1> + <h1>{collection.collection.name}</h1> </div> <div className="text-darkGray-HC-white text-sm font-bold"> {/* tags */} - <Tags tags={collection.tags} /> + <Tags tags={collection.collection.tags} /> </div> </div> </div> <div className="flex flex-row gap-3 pb-4 pt-4"> - <DownloadButton id={collection.id} objects={collection.collection_items} /> - <ShareButton id={collection.id} type={"colecao"} /> + <DownloadButton id={collection.collection.id} objects={collection?.resources} /> + <ShareButton id={collection.collection.id} type={"colecao"} /> </div> <div> {/* Publicador */} - <PublisherInfoCollection publisher={collection?.owner} /> + <PublisherInfoCollection publisher={collection?.owner[0]} /> </div> </div> </div> <div className="px-[25px] bg-ice-HC-dark"> <div> {/* recommendations */} </div> - <CollectionItems collection={collection} /> + <CollectionItems collection={collection.collection} resources={collection?.resources.resources || []}/> </div> </> )} diff --git a/src/app/components/About.js b/src/app/components/About.js index a67e4e8571cd4ce447e1a08d25e1e1c06549865b..acd2de96a056a370cf319709ef50b720fb04faae 100644 --- a/src/app/components/About.js +++ b/src/app/components/About.js @@ -16,68 +16,64 @@ import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded'; * @returns tela de sobre */ export default function AboutComponent() { - const [statistics, setStatistics] = useState({}); + // const [statistics, setStatistics] = useState({}); + // useEffect(() => { + // mecredApi + // .get("/statistics") + // .then(({ data }) => { + // setStatistics(data); + // }) + // .catch((error) => console.error(error)); + // }, []); - useEffect(() => { - mecredApi - .get("/statistics") - .then(({ data }) => { - setStatistics(data); - }) - .catch((error) => console.error(error)); - }, []); - - const StatisticInfo = ({ name, data, color, icon }) => { - return ( - <div className="flex flex-col items-center text-center max-sm:flex-row"> - <div - className={`h-24 w-24 my-5 mx-10 pt-5 flex justify-center rounded-full ${color["bg"]}`} - > - <Image - className={`rounded-lg w-14 h-14 invertIcon-HC-black`} - style={{}} - alt={name} - src={icon} - width={10} - height={10} - /> - </div> - <div> - <h1 className={`text-xl mb-1 font-bold max-sm:text-left ${color["text"]}`}>{data}</h1> - <h2 className={`text-base leading-tight font-bold max-sm:text-left ${color["text"]}`}>{name}</h2> - </div> - </div> - ); - }; - - const Statistics = () => { - return ( - <div className="flex max-sm:flex-col mb-10 gap-x-[60px]"> - <StatisticInfo - name={<p>Recursos Disponíveis</p>} - data={statistics["count"]} - color={{ text: "text-darkGray-HC-white", bg: "bg-orange-HC-white" }} - icon="/redigitais.svg" - /> - <StatisticInfo - name={<p>Recursos Acessados <br /> por mês</p>} - data={statistics["month_downloads"]} - color={{ text: "text-darkGray-HC-white", bg: "bg-violet-HC-white" }} - icon="/download.svg" - /> - <StatisticInfo - name={<p>Usuários Cadastrados </p>} - data={statistics["unique_users"]} - color={{ text: "text-darkGray-HC-white", bg: "bg-pink-HC-white" }} - icon="/seguir.svg" - /> - </div> - ); - }; - - + // const StatisticInfo = ({ name, data, color, icon }) => { + // return ( + // <div className="flex flex-col items-center text-center max-sm:flex-row"> + // <div + // className={`h-24 w-24 my-5 mx-10 pt-5 flex justify-center rounded-full ${color["bg"]}`} + // > + // <Image + // className={`rounded-lg w-14 h-14 invertIcon-HC-black`} + // style={{ }} + // alt={name} + // src={icon} + // width={10} + // height={10} + // /> + // </div> + // <div> + // <h1 className={`text-xl mb-1 font-bold max-sm:text-left ${color["text"]}`}>{data}</h1> + // <h2 className={`text-base leading-tight font-bold max-sm:text-left ${color["text"]}`}>{name}</h2> + // </div> + // </div> + // ); + // }; + // const Statistics = () => { + // return ( + // <div className="flex max-sm:flex-col mb-10"> + // <StatisticInfo + // name={<p>Recursos <br /> Disponíveis</p>} + // data={statistics["count"]} + // color={{ text: "text-orange-HC-white", bg: "bg-orange-HC-white" }} + // icon="/redigitais.svg" + // /> + // <StatisticInfo + // name={<p>Recursos <br /> Visualizados <br /> por mês</p>} + // data={statistics["month_downloads"]} + // color={{ text: "text-violet-HC-white", bg: "bg-violet-HC-white" }} + // icon="/download.svg" + // /> + // <StatisticInfo + // name={<p>Usuários <br /> Cadastrados </p>} + // data="31207" + // color={{ text: "text-pink-HC-white", bg: "bg-pink-HC-white" }} + // icon="/seguir.svg" + // /> + // </div> + // ); + // }; const ActorInfo = ({ name, description, nameImage }) => { return ( @@ -112,7 +108,7 @@ export default function AboutComponent() { </div> <div> <div className="flex flex-row justify-center"> - < Statistics /> + {/* <Statistics /> */} </div> </div> </div> diff --git a/src/app/components/AcessibilityBar.js b/src/app/components/AcessibilityBar.js index c3d6b6cbe869da4de053cd36c93b17c88f386038..28a87386606a0660e6aad42d144066b5bbf986cf 100644 --- a/src/app/components/AcessibilityBar.js +++ b/src/app/components/AcessibilityBar.js @@ -6,14 +6,14 @@ import { useRef, useLayoutEffect } from 'react'; import { useRouter } from 'next/navigation'; import { FaArrowCircleUp } from "react-icons/fa"; import { toggleContrast } from './themeUtils'; -import { isLoggedIn } from "@/app/handlers/loginHandler"; +import { useLoggedIn } from "@/app/handlers/loginHandler"; export default function AcessibilityBar() { const sizes = ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl', '8xl', '9xl']; const [defaultSizes, setDefaultSizes] = useState({}); const [sizeProperties, setSizeProperties] = useState({}); - const [loggedIn, setLoggedIn] = useState(false); + const loggedIn = useLoggedIn(); const router = useRouter(); const pathname = usePathname() @@ -25,11 +25,6 @@ export default function AcessibilityBar() { }); } - useEffect(() => { - // Remove erro de hidratação forçando o cliente a usar localStorage e tirando isso do server - setLoggedIn(isLoggedIn()); - }, []) - function changeFont(action) { sizes.forEach(size => { diff --git a/src/app/components/AllResults.js b/src/app/components/AllResults.js new file mode 100644 index 0000000000000000000000000000000000000000..35b0a6611541a72b8c2ec08f96e9b53efb90ffee --- /dev/null +++ b/src/app/components/AllResults.js @@ -0,0 +1,111 @@ +const { useEffect, useState, useRef } = require("react"); +import CardsLibrary from "./CardsLibrary"; +import mecredApi from "@/axiosConfig"; + +export default function AllResults({ filters, setFilters }) { + const [dataItems, setDataItems] = useState({}) + const abortControllersRef = useRef({}) + + + + const categories = [ + { name: "created_at", text: "mais recentes", entity: { collections: true, resources: true, users: true } }, + { name: "score", text: "mais relevantes", entity: { collections: true, resources: true, users: true } }, + { name: "comments", text: "mais comentados", entity: { collections: false, resources: true, users: false } }, + { name: "likes", text: "melhores avaliados", entity: { collections: false, resources: true, users: false } }, + { name: "dowloads", text: "mais baixados", entity: { collections: true, resources: true, users: false } }, + { name: "shares", text: "mais compartilhados", entity: { collections: true, resources: true, users: false } }, + { name: "followers", text: "mais seguidores", entity: { collections: false, resources: false, users: true } }, + { name: "views", text: "mais visualizados", entity: { collections: true, resources: true, users: false } } + ]; + + + + const entityLabels = { + collections: "Coleções", + resources: "Recursos", + users: "Usuários" + }; + + const prefixMap = { + resources: "re", + collections: "col", + users: "usr" + }; + + const nameMap = { + created_at: "mais_recentes", + score: "mais_relevantes", + comments: "mais_comentados", + likes: "mais_likes", + dowloads: "mais_downloads", + shares: "mais_shares", + followers: "mais_followers", + views: "mais_views" + }; + + const getKey = (categoryName, entityKey) => { + const prefix = prefixMap[entityKey] + const name = nameMap[categoryName] + return `${prefix}_${name}` + } + + useEffect(() => { + const fetchAllData = async () => { + try { + const { data } = await mecredApi.get("public/elastic/home") + setDataItems(data) + } catch (err) { + if (err.name === 'CanceledError' || err.name === 'AbortError') { + console.log('requisição cancelada') + } else { + console.error('erro na requisição', err) + } + } + } + + fetchAllData() + + }, []) + + return ( + <div className={`w-screen overflow-auto mt-20`}> + <div className="bg-white flex flex-col justify-between rounded-md max-w-screen-xl h-[28rem] mb-20 p-4 shadow"> + <p className="text-2xl font-semibold text-darkGray-HC-white mb-4">Coleções Recomendadas pelo MEC </p> + <div className="flex gap-4"> + <CardsLibrary page="Coleções" data={dataItems['col_mais_relevantes']} /> + </div> + <button className="flex justify-center items-center hover:bg-ice rounded-md font-semibold text-darkGray-HC-white h-10 mt-4"> + Ver mais... + </button> + </div> + {categories.map(category => ( + <div key={category.name}> + {Object.entries(category.entity).map(([entityKey, isEnabled]) => ( + isEnabled && ( + <div + key={entityKey} + className={`bg-white flex flex-col justify-between rounded-md mr-56 mb-20 p-4 shadow ${entityLabels[entityKey] === 'Usuários' ? 'h-[32rem]' : 'h-[28rem]'}`}> + <p className="text-2xl font-semibold text-darkGray-HC-white mb-8 mt-2 mx-4"> + {entityLabels[entityKey]} {category.text} + </p> + <div className={`flex gap-4 mx-4 overflow-hidden ${entityLabels[entityKey] === 'Usuários' ? 'h-[32rem]' : 'h-[20rem]'} `}> + <CardsLibrary + page={entityLabels[entityKey]} + data={dataItems[getKey(category.name, entityKey)]} + /> + </div> + + <button + onClick={() => setFilters(prev =>({...prev, entidade: entityKey, categoria: category.name }))} + className="flex sticky justify-center bg-white items-center hover:bg-ice rounded-md font-semibold text-darkGray-HC-white h-10 mt-4"> + Ver mais... + </button> + </div> + ) + ))} + </div> + ))} + </div> + ); +} diff --git a/src/app/components/Cards.js b/src/app/components/Cards.js index c1da0b8127475dce3ec6306a2fca52548d2f91d9..6bc9b8f972bd61eaf3e346f8831f9b5848f6de4b 100644 --- a/src/app/components/Cards.js +++ b/src/app/components/Cards.js @@ -96,6 +96,7 @@ export default function Cards(props) { return thumbnail_url; }; + /** * * @param {String} updated_time @@ -177,7 +178,6 @@ export default function Cards(props) { }} > - <CardMedia id="conteudo" tabIndex="0" @@ -188,9 +188,9 @@ export default function Cards(props) { }} //component="img" image={ - props["image"] === null - ? getDefaultThumbnail(props["type"]) - : mecredURL + props["image"] + props.image === null + ? getDefaultThumbnail(props.type) + : props.image } alt="imagem" title={props.title} @@ -221,38 +221,30 @@ export default function Cards(props) { </> : <> - <Link href={`/recurso/${props.id}${props.collectionSource ? `?collectionId=${props.collectionSource}` : ""}`} className="w-full"> - <div className="flex flex-row mt-4 mb-3 w-full justify-start cursor-pointer"> - {props?.noAvatar || !props["avatar"] ? ( - <div className={`flex items-center shrink-0 justify-center text-xl font-bold ml-1 text-ice-HC-dark rounded-full h-[33px] w-[33px] ${getRandomBg(props["id"])}`}> - {props["author"][0]} - </div> - ) : ( - <img - src={mecredURL + props["avatar"]} - alt={props["author"]} - className="w-[33px] h-[33px] object-cover rounded-full" - /> - )} - <div className="mx-2"> - <div className="line-clamp-1 text-sm font-bold text-darkGray-HC-white-underline mb- w-[100%] min-h-4"> - {props["title"]} - </div> - <div className="flex flex-row"> - <div className="flex flex-col"> - <div> - <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light"> - {props["author"]} - </div> - <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light"> - {timeFunction(props.updated_at)} - </div> - </div> + <div className="flex flex-row mt-4 mb-4 w-full justify-start "> + {props?.noAvatar || !props["avatar"] ? <div className={`flex items-center shrink-0 justify-center text-xl font-bold ml-1 text-ice-HC-dark rounded-full h-[33px] w-[33px] ${getRandomBg(props["id"])}`} >{props["author"][0]}</div> + : + <img + src="https://s3.c3sl.ufpr.br/mecredteste/avatar/1" + alt={props["author"]} + className="w-[33px] h-[33px] object-cover rounded-full" + + /> + + } + <div className="mx-2"> + <div className="line-clamp-1 text-sm font-bold text-darkGray-HC-white-underline mb- w-[100%] min-h-4"> {props["title"]} </div> + <div className="flex flex-row"> + <div> + <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light" > + {props["author"]} + </div> + <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light"> + {timeFunction(props.updated_at)} </div> </div> </div> </div> - </Link> {userProfile && props.profilePage === "recurso" ? <div className="flex justify-end w-full"> <DeleteModal setUniqueResources={props.setUniqueResources} userProfile open={deleteOpen} onClose={() => setDeleteOpen(false)} idLogin={userData["id"]} resourceId={props["id"]} /> @@ -270,6 +262,7 @@ export default function Cards(props) { </div> : null } + </div> </> } {props?.homologa == "submitted" ? diff --git a/src/app/components/CardsLibrary.js b/src/app/components/CardsLibrary.js new file mode 100644 index 0000000000000000000000000000000000000000..0c5e630e067add17c19a6089868c0d6d64353aec --- /dev/null +++ b/src/app/components/CardsLibrary.js @@ -0,0 +1,217 @@ +import PersonAddIcon from '@mui/icons-material/PersonAdd'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import ContentPasteIcon from '@mui/icons-material/ContentPaste'; +import { s3URL } from '@/axiosConfig'; + +const timeFunction = (updated_time) => { + let data = new Date(updated_time) + let dataAtual = new Date(); + + let time = dataAtual.getTime() - data.getTime(); + let dia = Math.floor(time / (1000 * 60 * 60 * 24)); + let ano; + let mes; + + if ((ano = Math.floor(dia / 365)) > 0) + return <p> há {ano} {ano === 1 ? "ano" : "anos"} </p> + else if (((mes = Math.floor(dia / 31)) > 0)) + return <p> há {mes} {mes === 1 ? "mês" : "meses"}</p> + + if (dia === 0) + return <p>hoje</p> + return <p>há {dia} {dia === 1 ? "dia" : "dias"}</p> + +} + +function getRandomBg(id) { + const colors = [ + "bg-turquoise", + "bg-orange", + "bg-turquoise-hover", + "bg-darkOrange-HC-gray ", + "bg-violet", + "bg-pink", + "bg-red", + "bg-darkGray-HC-white", + "bg-darkGray-HC-white-click", + "bg-ice-HC-dark ", + "bg-darkGray-HC-white", + "bg-darkGray-HC-white", + "bg-turquoise-HC-dark", + ] + + return colors[id % colors.length]; +} + + + + + +function AvatarUsuario({ src, alt = "Avatar" }) { + return src ? + <img + src={src} + alt={alt} + className="w-[45px] h-[45px] bg-darkGray-HC-dark rounded-full object-cover" + /> + : <div className="flex items-center shrink-0 justify-center text-xl font-bold ml-1 text-ice-HC-dark rounded-full h-[33px] w-[33px] bg-gray-300"> + A + </div> +} + + + +function CardsResources({ data }) { + return ( + <> + <div className='flex flex-wrap gap-2'> + {Array.isArray(data) && data?.map((item, index) => ( + <div key={item._id || index} className='mt-4 ' > + <img + tabIndex="0" + src={`${s3URL}` + "thumbnail/resource/" + `${item._id}`} + alt="imagem" + title="Título do conteúdo" + className="hover:scale-[1.02] p-1 focus:border-turquoise-HC-white focus:border-4 border-gray-color transition-transform rounded-xl aspect-video w-[327px] h-[181px] object-cover" + /> + + + <div className="flex w-64 mt-1"> + <AvatarUsuario /> + <div className="flex flex-col ml-4 max-sm:ml-0 max-sm:justify-stretch"> + <div className="line-clamp-2 text-lg font-bold text-darkGray-HC-white-underline min-h-4 max-sm:w-full"> + {item._source.name || "sem título"} + </div> + <div className="flex flex-row"> + <div className="flex flex-col"> + <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light"> + {item._source.author || "autor desconhecido"} + </div> + <div className="flex items-center gap-1 text-darkGray-HC-white text-sm font-light"> + <span className="text-gray-400 text-xl">★</span> + <p>{item._source.views}</p> + <span className="inline-block w-1.5 h-1.5 bg-gray-400 rounded-full shrink-0 mx-1"></span> + <p>{item._source.created_at} dias</p> + <span className="inline-block w-1.5 h-1.5 bg-gray-400 rounded-full shrink-0 mx-1"></span> + <p className='shrink-0'>{item._source.views} visualizações</p> + </div> + </div> + </div> + </div> + </div> + </div> + ))} + </div> + </> + ) +} + + +function CardCollections({ }) { + return <div className="flex mt-4 "> + <AvatarUsuario /> + <div className="flex flex-col ml-4 max-sm:ml-0 max-sm:justify-stretch"> + <div className="line-clamp-2 text-lg font-bold text-darkGray-HC-white-underline min-h-4 max-sm:w-full"> + Título da colecao + </div> + <div className="flex flex-row"> + <div className="flex flex-col"> + <div className="line-clamp-1 text-darkGray-HC-white text-sm font-light"> + Autor Exemplo + </div> + <div className="flex items-center gap-1 text-darkGray-HC-white text-sm font-light"> + + <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}> + <path strokeLinecap="round" strokeLinejoin="round" d="M12 2l9 6-9 6-9-6 9-6z" /> + <path strokeLinecap="round" strokeLinejoin="round" d="M12 14l9-6v6l-9 6-9-6v-6l9 6z" /> + </svg> + + <span>15 recursos</span> + <span className="inline-block w-1.5 h-1.5 bg-gray-400 rounded-full mx-2"></span> + <span>há 2 dias</span> + </div> + + </div> + </div> + </div> + </div> + +} + +function CardUsers({ }) { + return ( + <div className="bg-[#f0f6f7] rounded-xl p-6 w-[280px] text-center text-gray-600 relative select-none"> + {/* ícone de adicionar usuário no topo esquerdo */} + {/* onclick para seguir, onclick para deseguir se ja segue */} + <div className="absolute top-4 left-4 text-gray-400"> + <PersonAddIcon /> + </div> + + + {/* avatar */} + <div className="mx-auto mb-4 w-24 h-24 rounded-full bg-gray-300 flex items-center justify-center text-white text-4xl font-bold"> + R + </div> + + {/* nome */} + <h2 className="font-semibold text-darkGray-HC-white-underline text-lg mb-1">Roberto Pereira</h2> + + {/* profissão */} + <p className="line-clamp-1 text-darkGray-HC-white-underline text-sm mb-0.5">Professor da E.E. Américo...</p> + + {/* local */} + <p className="text-darkGray-HC-white-underline text-sm mb-6">Curitiba (PR)</p> + + {/* linha separadora */} + <hr className="border-gray-300 mb-6" /> + + {/* informações com ícones */} + <div className="grid grid-cols-2 gap-y-4 text-darkGray-HC-white-underline text-xs font-light"> + + {/* recursos */} + <div className="flex items-center gap-2 "> + <ContentPasteIcon fontSize="small" /> + <span>Recursos 25</span> + </div> + + {/* seguidores */} + <div className="flex items-center gap-2"> + <span>Seguidores 23</span> + </div> + + {/* insígnias */} + <div className="flex items-center gap-2"> + <EmojiEventsIcon fontSize="small" /> + <span>Insígnias 6</span> + </div> + + {/* seguindo */} + <div className="flex items-center gap-2"> + + <span>Seguindo 126</span> + </div> + + </div> + </div> + ); + +} + + +export default function CardsLibrary({ page, data }) { + return ( + <div + tabIndex="-1" + className=" transition ease-in-out active:bg-ice-HC-dark active:rounded-3xl" + > + + {page === "Recursos" && <CardsResources data={data} />} + + {page === "Coleções" && <CardCollections />} + + {page === "Usuários" && <CardUsers />} + + + </div> + ); +} diff --git a/src/app/components/Content.js b/src/app/components/Content.js index 51baea37c829305cf8d60119b6054b4d3b241afd..f6bde4f1d5700b1bc3e446ea777c42d7a5c9373f 100644 --- a/src/app/components/Content.js +++ b/src/app/components/Content.js @@ -1,120 +1,246 @@ "use client" import { useEffect, useState } from "react"; -import InfiniteScroll from "../components/InfiniteScroll"; import Overlay from "../components/Overlay"; -import GroupButtonsFilters from "./GroupButtonsFilters"; -import FiltersModal from "./FiltersModal"; -import { useSearchParams, useRouter } from "next/navigation"; - -function tradutor(name) { - switch (name) { - case "LearningObject": - return "Recursos" - case "Collection": - return "Coleções" - case "MEC": - return "MEC" - case "User": - return "Usuários" - default: - return +import TuneIcon from '@mui/icons-material/Tune'; +import { useRouter, usePathname } from "next/navigation"; +import TypeAndFilterModal from "./TypeAndFilterModal"; +import SearchIcon from '@mui/icons-material/Search'; +import AllResults from "./AllResults"; +import InfiniteScrollResults from "./InfiniteScrollResults"; +import CancelIcon from '@mui/icons-material/Cancel'; + + + +//tags quando os filtros estao ativados +function TagsFilters({ filters, setFilters }) { + + const handleRemoveTag = (type, value) => { + const updated = { ...filters } + updated[type] = updated[type].filter(item => item !== value) + setFilters(updated) } + + return ( + <div className="flex flex-wrap"> + {["formato", "nivel", "idioma", "materias"].map(type => ( + filters[type]?.map(item => ( + <div + key={`${type}-${item}`} + className="flex items-center bg-darkOrange-HC-white text-white rounded-full px-3 py-1 mr-2 mb-2 text-sm" + > + <span>{item}</span> + <button + onClick={() => handleRemoveTag(type, item)} + className="ml-2 hover:bg-darkOrange-HC-gray rounded-full w-4 h-4 flex items-center justify-center text-xs" + > + <CancelIcon + color="disabled" + sx={{ opacity: 0.8 }} + className="group-hover:opacity-100" + /> + </button> + </div> + )) + ))} + </div> + ) } -function filterTradutor(name) { - switch (name) { - case "publicationdesc": - return "recentes" - case "score": - return "relevantes" - case "likes": - return "colecionados" - case "downloads": - return "visualizados" - } +//funcao dos botoes de filtros +function CategoryAndFilterButtons({ searchParams, setFilterIsActive, filterIsActive, filters, setFilters }) { + const router = useRouter(); + const pathname = usePathname(); + const [open, setOpen] = useState(false); + const handleOpen = () => setOpen(true); + const handleClose = () => setOpen(false); + + + const categories = [ + { name: "tudo", text: "Tudo" }, + { name: "mec", text: "MEC Recomenda" }, + { name: "created_at", text: "+ Recentes" }, + { name: "score", text: "+ Relevantes" }, + { name: "comments", text: "+ Comentados" }, + { name: "likes", text: "+ Avaliados" }, + { name: "downloads", text: "+ Baixados" }, + { name: "views", text: "+ Visualizados" }, + { name: "saves", text: "+ Salvos" }, + { name: "shares", text: "+ Compartilhados" }, + { name: "approved_resources", text: "+ Recursos" }, // só para usuario + ]; + + + const updateSearchParams = + (name, value) => { + const params = new URLSearchParams(searchParams); + params.set(name, value) + const url = pathname + '?' + params.toString(); + router.push(url); + } + + return <div className={`fixed ml-36 w-[88%] bg-fundo bg-repeat bg-fixed ${(filters.pesquisa || filters.categoria !== "tudo") ? "mt-12" : ""}`}> + + <div className={` flex flex-row gap-1 pb-4 overflow-auto max-md:ml-3 z-30 animate-scrollHint`}> + {categories.map(({ name, text }) => { + return <button key={name} onClick={() => updateSearchParams("categoria", name)} className={"p-2 rounded-md shrink-0 " + (filters.categoria === name ? "bg-turquoise-HC-dark text-white" : "bg-lightGray-HC-dark text-darkGray-HC-white")}> {text}</button> + })} + <button startIcon={<TuneIcon />} onClick={handleOpen} className='bg-darkGray-HC-white hover:bg-mediumGray-HC-dark font-semibold normal-case text-white-HC-dark-underline hover:text-white rounded-lg px-3 outline outline-1 outline-ice-HC-white'>Filtros</button> + <TypeAndFilterModal searchParams={searchParams} setFilterIsActive={setFilterIsActive} open={open} handleClose={handleClose} filters={filters} setFilters={setFilters} /> + {filterIsActive && <button onClick={() => { router.push(pathname); setFilterIsActive(false) }} className='bg-darkGray-HC-white shrink-0 hover:bg-mediumGray-HC-dark font-semibold normal-case text-white-HC-dark-underline hover:text-white rounded-lg px-3 mr-2 outline outline-1 outline-ice-HC-white'> + Limpar Filtros </button>} + </div> + {filterIsActive && <TagsFilters setFilterIsActive={setFilterIsActive} searchParams={searchParams} filters={filters} setFilters={setFilters} />} + </div> } -/** - * @param {Object} props - * @param {string} props.name informna se é recursos, coleções ou MEC - * @param {string} props.inputFilter - */ -export default function Content({ name, inputFilter, searchPage }) { - const [titlePage, setTitlePage] = useState("Recentes"); - const [newSize, setNewSize] = useState(false); - const [activeFilters, setActiveFilters] = useState(false) - const router = useRouter() +//funcao de diltrar entre usuario, recurso, colecoes e mensagem da query +function FilterMessageAndCategory({ searchParams = { searchParams } }) { + + const [entity, setEntity] = useState(searchParams["entidade"] ? searchParams["entidade"] : "resources"); + const query = searchParams["pesquisa"] ? searchParams["pesquisa"] : ""; + const params = new URLSearchParams(searchParams); + const pathname = usePathname(); + const router = useRouter(); + + + const labelMap = { + resources: "Recursos", + collections: "Coleções", + users: "Usuários", + }; + + return <div className="fixed flex flex-row gap-1 h-78 ml-36 overflow-auto w-full bg-fundo bg-repeat bg-fixed max-md:ml-3 z-30 animate-scrollHint"> + <div className="flex "> + {query !== "" && <p className=" text-darkGray-HC-white-underline text-lg"> + <SearchIcon /> Exibindo resultados da busca por <a className="font-bold"> "{query}" </a> na categoria: + </p>} + <div className="flex text-darkGray-HC-white-underline ml-4 gap-8 items-center"> + {["resources", "collections", "users"].map((label) => ( + <label key={label} className="flex items-center gap-2 cursor-pointer text-gray-700 text-lg"> + <input + type="radio" + name="entity" + value={label} + checked={entity === label} + onChange={() => { + setEntity(label); params.set("entidade", label); const url = pathname + '?' + params.toString(); + router.push(url); + }} + className="accent-cyan-500 w-4 h-4" + /> + {labelMap[label]} + </label> + ))} + </div> + </div> + </div> +} + + +//AQUI: +//QUANDO CLICA EM +RECENTE, POR PADRAO VAI PARA RECURSOS +// APARECEER: EXIBINDO RESULTADOS DA BUSCA NA CATEGORIA ... +export default function Content({ searchParams }) { + const [filterIsActive, setFilterIsActive] = useState(false); + + const [filters, setFilters] = useState({ + pesquisa: searchParams["pesquisa"] ? searchParams["pesquisa"] : "", + formato: searchParams["formato"] ? searchParams["formato"].split(",") : [], + nivel: searchParams["nivel"] ? searchParams["nivel"].split(",") : [], + idioma: searchParams["idioma"] ? searchParams["idioma"].split(",") : [], + materias: searchParams["materias"] ? searchParams["materias"].split(",") : [], + entidade: searchParams["entidade"] ? searchParams["entidade"] : "resources", + categoria: searchParams["categoria"] ? searchParams["categoria"] : "tudo" - const [filterState, setFilterState] = useState({ - languages: [], // em ids - searchClass: searchPage ? searchPage : "LearningObject", // lo, user, collection - subjects: [], // matéria (portugues, matemática) - objectTypes: [], // pdf, video, etc - educationalStages: [], // ensino fundamental, médio, etc - query: "*", // string de busca - order: "publicationdesc", // ordem - title: "Recentes" }); - useEffect(() => { - setTitlePage(filterState.title) - }, [filterState]) - - const itemsBySearchClass = {}; - const setItemsBySearchClass = {}; - const abortControllerBySearchClass = {}; - const setAbortControllerBySearchClass = {}; - - [abortControllerBySearchClass["LearningObject"], setAbortControllerBySearchClass["LearningObject"]] = useState(new AbortController()); - [abortControllerBySearchClass["Collection"], setAbortControllerBySearchClass["Collection"]] = useState(new AbortController()); - [abortControllerBySearchClass["User"], setAbortControllerBySearchClass["User"]] = useState(new AbortController()); - [abortControllerBySearchClass["MEC"], setAbortControllerBySearchClass["MEC"]] = useState(new AbortController()); - [itemsBySearchClass["LearningObject"], setItemsBySearchClass["LearningObject"]] = useState([]); - [itemsBySearchClass["Collection"], setItemsBySearchClass["Collection"]] = useState([]); - [itemsBySearchClass["User"], setItemsBySearchClass["User"]] = useState([]); - [itemsBySearchClass["MEC"], setItemsBySearchClass["MEC"]] = useState([]); + const router = useRouter() + const pathname = usePathname() + //funcao de montar apartir da url + const getFiltersFromSearchParams = (searchParams) => { + const params = new URLSearchParams(searchParams) + + return { + pesquisa: params.get("pesquisa") || "", + formato: params.get("formato") ? params.get("formato").split(",") : [], + nivel: params.get("nivel") ? params.get("nivel").split(",") : [], + idioma: params.get("idioma") ? params.get("idioma").split(",") : [], + materias: params.get("materias") ? params.get("materias").split(",") : [], + entidade: params.get("entidade") || "resources", + categoria: params.get("categoria") || "tudo" + } + } + + // monta os filters baseado na url quando searchParams mudar useEffect(() => { - for (const [searchClass, abortController] of Object.entries(abortControllerBySearchClass)) { - setItemsBySearchClass[searchClass]([]); - abortController.abort(); - if (searchClass === searchPage) { - setAbortControllerBySearchClass[searchClass](new AbortController()); + const newFilters = getFiltersFromSearchParams(searchParams) + + //evita atualizar se já está igual, previnindo loop + const isSame = JSON.stringify(newFilters) === JSON.stringify(filters) + if (!isSame) { + setFilters(newFilters) + + const { entidade, categoria, ...filtersWithoutEntidade } = newFilters; + + const isFiltersNotEmpty = Object.values(filtersWithoutEntidade).some( + value => value.length > 0 + ); + + if (isFiltersNotEmpty) { + setFilterIsActive(true); + } else { + setFilterIsActive(false); } } - setFilterState(old => { return { ...old, searchClass: searchPage, query: (inputFilter === null ? "*" : inputFilter) } }) - }, [inputFilter, searchPage]) - {/* GroupButtonsFilters: Botões para seleção do tipo de filtro usado (selectFilter) */ } - //caso MEC não apresenta o GroupButtonsFilters pois não há conteudo suficiente para ser filtrado - return ( - <Overlay filterState={filterState} setFilterState={setFilterState} setNewSize={setNewSize} newSize={newSize} type="twoColumns"> - <> - {(filterState.searchClass !== "MEC" && filterState.searchClass != "User") ? - ( - <div> - <div className="pl-3 max-sm:pl-1 fixed w-full bg-fundo bg-repeat bg-fixed max-md:ml-3 z-30" > - <h1 className="text-2xl ml-5 font-bold text-darkGray-HC-white"> - {tradutor(filterState.searchClass)} {(filterState.order === "title") ? "por Ordem alfabética" : " mais " + titlePage} - </h1> - <div className={`flex w-full justify-between`}> - <GroupButtonsFilters pageName={searchPage} activeFilters={activeFilters} setActiveFilters={setActiveFilters} filterState={filterState} setFilterState={setFilterState} setItems={setItemsBySearchClass[filterState.searchClass]} /> - </div> - </div> - {/*caso tenha mais de 15 filtros ativos, o InfiniteScroll é renderizado com um padding-top maior, esse valor foi definido empiricamente*/} - <div className="pt-36 max-md:pt-28"> - <InfiniteScroll filterState={filterState} setNewSize={setNewSize} setItems={setItemsBySearchClass[filterState.searchClass]} items={itemsBySearchClass[filterState.searchClass]} abortController={abortControllerBySearchClass[filterState.searchClass]} /> - </div> - </div> - ) - : - <> - <InfiniteScroll filterState={filterState} setNewSize={setNewSize} setItems={setItemsBySearchClass[filterState.searchClass]} items={itemsBySearchClass[filterState.searchClass]} abortController={abortControllerBySearchClass[filterState.searchClass]} /> - </> - } - </> - </Overlay> - ) -} \ No newline at end of file + }, [searchParams]) + + + + //funcao que monta a url + const getUrlFromFilters = (pathname, filters) => { + const params = new URLSearchParams() + + if (filters.pesquisa) params.set("pesquisa", filters.pesquisa) + if (filters.formato.length > 0) params.set("formato", filters.formato.join(",")) + if (filters.nivel.length > 0) params.set("nivel", filters.nivel.join(",")) + if (filters.idioma.length > 0) params.set("idioma", filters.idioma.join(",")) + if (filters.materias.length > 0) params.set("materias", filters.materias.join(",")) + if (filters.categoria) params.set("categoria", filters.categoria) + if ((filters.categoria) !== "tudo" && filters.entidade) params.set("entidade", filters.entidade) + + return pathname + "?" + params.toString() + } + + //sempre que filters mudar, atualiza a url + useEffect(() => { + router.replace(getUrlFromFilters(pathname, filters)) + const { entidade, categoria, ...filtersWithoutEntidade } = filters; + + const isFiltersNotEmpty = Object.values(filtersWithoutEntidade).some( + value => value.length > 0 + ); + + if (isFiltersNotEmpty) { + setFilterIsActive(true); + } else { + setFilterIsActive(false); + } + }, [filters]) + + + + return <Overlay searchParams={searchParams} > + {((filters.pesquisa !== "") || (filters.categoria !== "tudo")) && <FilterMessageAndCategory searchParams={searchParams} filter={filters} />} + <CategoryAndFilterButtons setFilterIsActive={setFilterIsActive} filterIsActive={filterIsActive} searchParams={searchParams} filters={filters} setFilters={setFilters} /> + {/* sem filtraar os botoes ali*/} + {filters.categoria === "tudo" ? + <AllResults filters={filters} setFilters={setFilters} /> + : + <InfiniteScrollResults filters={filters} /> + } + </Overlay>; +} diff --git a/src/app/components/DownloadButton.js b/src/app/components/DownloadButton.js index 68788e07800d40d0cff1075be9b7d32a03c7f659..229f5f500c6541b1ca3cda8c898dae449ab8da59 100644 --- a/src/app/components/DownloadButton.js +++ b/src/app/components/DownloadButton.js @@ -1,6 +1,6 @@ import * as React from 'react'; import DownloadOutlinedIcon from '@mui/icons-material/Download'; -import { mecredURL, mecredURLv1 } from '@/axiosConfig'; +import mecredApi, { mecredURL, mecredURLv1 } from '@/axiosConfig'; /** * Responsável pelo Download dos recursos de uma coleção @@ -10,28 +10,34 @@ import { mecredURL, mecredURLv1 } from '@/axiosConfig'; */ export default function DownloadButton({ id, objects }) { - const pathAux = typeof window !== 'undefined' ? window.location.pathname : ''; - const path = pathAux.split('/')[1]; - const handleDownloadCollection = (e) => { + const handleDownloadCollection = async (e) => { e.preventDefault(); - let zip = false; - - alert("Caso esta coleção contenha links externos, eles serão abertos em novas abas.\n Talvez seja preciso permitir a abertura de várias abas nas opções do seu navegador!"); - - objects.forEach(async (object) => { - if (object.collectionable.link !== null) { - let url = mecredURLv1 + `/learning_objects/${object.collectionable.id}/download`; - window.open(url, '_blank'); - } else { - zip = true; - } - }); - - if (zip) { - window.location = mecredURL + `zipper/${id}`; + + try { + const response = await mecredApi.get( + `/public/collections/${id}/download`, + { responseType: "blob" } // importante para baixar arquivos + ); + + const url = window.URL.createObjectURL(response.data); + const a = document.createElement("a"); + a.href = url; + a.download = `collection-${id}.zip`; // ou outro nome apropriado + a.click(); + window.URL.revokeObjectURL(url); + + } catch (error) { + if (error.response?.data?.message) { + alert(error.response.data.message); // substitua por toast/modal se preferir + } else { + alert("Erro ao baixar a coleção."); + } + console.error(error); } - }; + }; + + return ( <button diff --git a/src/app/components/FiltersModal.js b/src/app/components/FiltersModal.js deleted file mode 100644 index deb92008225133e335b816cb1a61d3563fc134c3..0000000000000000000000000000000000000000 --- a/src/app/components/FiltersModal.js +++ /dev/null @@ -1,83 +0,0 @@ -import * as React from 'react'; -import { useState } from 'react'; -import Button from '@mui/material/Button'; -import Modal from '@mui/material/Modal'; -import TuneIcon from '@mui/icons-material/Tune'; -import CloseIcon from '@mui/icons-material/Close'; -import FormFilters from './FormFilters'; - - - -/** - * - * @returns modal de filtros - */ -export default function FiltersModal({ - scholarityLevelsAvailable, - languagesAvailable, - setFilterState, - filterState, - setItems, - activeFilters, - setActiveFilters, - subjectsAvailable -}) { - const [open, setOpen] = useState(false); - const handleOpen = () => setOpen(true); - const handleClose = () => setOpen(false); - - return ( - <div className='justify-self-end'> - <Modal - open={open} - onClose={handleClose} - aria-labelledby="modal-modal-title" - aria-describedby="modal-modal-description" - className='grid place-items-center ' - slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário - }, - }, - }} - > - <div className='flex flex-col w-[60%] h-[80%] bg-white-HC-dark overflow-x-auto rounded-lg outline outline-1 outline-ice-HC-white'> - <div> - <div className='fixed z-20 w-[60%]'> - <div className='flex justify-between bg-white-HC-dark rounded-lg p-4'> - <p className=' text-2xl font-bold text-darkGray-HC-white '> - Filtros de Pesquisa - </p> - <CloseIcon className="cursor-pointer" onClick={handleClose} sx={{ color: "#6c8080", fontSize: "35px" }} /> - </div> - </div> - <div className='p-6'> - <FormFilters - activeFilters={activeFilters} - setActiveFilters={setActiveFilters} - handleClose={handleClose} - scholarityLevelsAvailable={scholarityLevelsAvailable} - languagesAvailable={languagesAvailable} - setFilterState={setFilterState} - filterState={filterState} - setItems={setItems} - subjectsAvailable={subjectsAvailable} - /> - </div> - </div> - </div> - </Modal> - <div className='justify-self-end'> - <div className='flex ml-2 mt-2'> - {activeFilters && - <Button onClick={() => { setActiveFilters(false) }} href="/busca?page=LearningObject" className={`normal-case font-semibold text-sm bg-darkGray-HC-white rounded-lg min-w-32 mx-1 text-white-HC-dark-underline hover:bg-slate-300`}> - Limpar Filtros - </Button> - } - <Button startIcon={<TuneIcon />} onClick={handleOpen} className='bg-darkGray-HC-white hover:bg-mediumGray-HC-dark font-semibold normal-case text-white-HC-dark-underline hover:text-white rounded-lg px-3 mr-2 outline outline-1 outline-ice-HC-white'>Filtros</Button> - </div> - </div> - </div> - ); -} diff --git a/src/app/components/GovBrSignInButton.js b/src/app/components/GovBrSignInButton.js new file mode 100644 index 0000000000000000000000000000000000000000..2307ec1e95bc3c51e651cb0f5e95f28228a10a02 --- /dev/null +++ b/src/app/components/GovBrSignInButton.js @@ -0,0 +1,19 @@ +import { mecredURL } from "@/axiosConfig"; + +export function GovBRSignInButton() { + return <> + <a href={mecredURL + "/public/auth/signingovbr"}> + <button + className="inline-flex w-full justify-center items-center font-medium text-md text-govBrBlue-HC-white bg-govBrBG-HC-dark hover:bg-govBrHover-HC-dark active: active:bg-govBrActive-HC-white px-4 h-10 outline outline-1 outline-ice-HC-white rounded-full" + type="button"> + Entrar com + <img + className="max-h-[20px]" src="https://www.gov.br/++theme++padrao_govbr/img/govbr-colorido-b.png" + style={{ filter: "var(--filter)" }} + alt="gov.br" /> + </button> + </a> + </>; +} + + diff --git a/src/app/components/GroupButtonsFilters.js b/src/app/components/GroupButtonsFilters.js index eae98a11618ddfc169c0730f5f7fbad509e65f9d..73a84933db867a14933db19e1764a6581879fe16 100644 --- a/src/app/components/GroupButtonsFilters.js +++ b/src/app/components/GroupButtonsFilters.js @@ -1,6 +1,6 @@ import { Button } from "@mui/material"; import { usePathname } from "next/navigation"; -import FiltersModal from "./FiltersModal"; +import FiltersModal from "./TypeAndFilterModal"; import { Chip } from "@mui/material"; import { useState, useEffect } from "react"; import mecredApi from "@/axiosConfig"; @@ -22,40 +22,46 @@ export default function GroupButtonsFilters({ pageName, filterState, setFilterSt const [languagesAvailable, setLanguagesAvailable] = useState([]); const [subjectsAvailable, setSubjectsAvailable] = useState([]); - useEffect(() => { - mecredApi.get("/educational_stages") - .then((response) => { setScholarityLevelAvailable(response.data) }); - - mecredApi.get("/languages") - .then((response) => { setLanguagesAvailable(response.data) }); - - mecredApi - .get("/subjects") - .then(({ data }) => { - setSubjectsAvailable(data); - }) - }, []); + const fetchData = async () => { + try { + const [scholarityRes, languageRes, subjectsRes] = await Promise.all([ + mecredApi.get("public/educationalStage/all"), + mecredApi.get("public/language/all"), + mecredApi.get("public/subjects/all"), + ]); + + setScholarityLevelAvailable(scholarityRes.data); + setLanguagesAvailable(languageRes.data); + setSubjectsAvailable(subjectsRes.data); + } catch (error) { + console.error("Erro ao buscar dados para filtro:", error); + } + }; + + fetchData(); + }, []); + const pathname = usePathname(); const atalhos = [ { title: 'Recentes', titlePage: 'Recentes', - order: 'publicationdesc' + order: 'created_at' }, { title: 'Relevantes', titlePage: 'Relevantes', order: 'score' }, - pageName === "Collection" ? null : + pageName === "collections" ? null : { title: 'Colecionados', titlePage: 'Colecionados', order: 'likes' }, - pageName === "LearningObject" ? + pageName === "resources" ? { title: 'Visualizados', titlePage: 'visualizados', diff --git a/src/app/components/GroupCardsCollections.js b/src/app/components/GroupCardsCollections.js index a025716679b6551a8a254d7065369667a3ac2b49..f3d5fd8bb9d1eba6317bdf725927fc7485699b8f 100644 --- a/src/app/components/GroupCardsCollections.js +++ b/src/app/components/GroupCardsCollections.js @@ -2,6 +2,8 @@ import { useEffect, useState } from "react"; import Cards from "./Cards"; import { useRouter } from "next/navigation" +import mecredApi from "@/axiosConfig"; +import { authHeaders } from "../handlers/loginHandler"; /** * Retorna na tela uma coleção * @param {Array.<Object>} data - recursos da coleção @@ -10,9 +12,9 @@ import { useRouter } from "next/navigation" export default function GroupCardsCollections({ data, cardsPerRow, collectionId }) { const [expanded, setExpanded] = useState(false); const [showButton, setShowButton] = useState(false); + const [resources, setResources] = useState(null) const router = useRouter() - const handleResize = (data) => { setShowButton(data?.length > cardsPerRow); }; @@ -25,6 +27,37 @@ export default function GroupCardsCollections({ data, cardsPerRow, collectionId handleResize(data); }), [cardsPerRow]; + useEffect(() => { + const fetchResources = async () => { + // if (idLogin === id) { + + await mecredApi + .get(`public/collection-resources/${collectionId}/resources`, { + headers: authHeaders(), + }) + /** + * Além de setar as coleções, indica o número total de coleções + * Isso facilita na verificação + */ + .then(({ data, headers }) => { + setResources(data) + + setGot(true) + }); + // } else { + // await mecredApi + // .get(`public/collections/${id}/collections`) + // .then(({ data, headers }) => { + // setTotalCount(headers["x-total-count"]) + // setCollections(data) + // console.log(data) + // setGot(true) + // }); + // } + }; + fetchResources(collectionId); + }, []); + return ( <div className="flex flex-col"> @@ -32,18 +65,18 @@ export default function GroupCardsCollections({ data, cardsPerRow, collectionId <div id="contentSize" className={`flex content flex-wrap max-sm:justify-center mb-4 max-sm:ml-0 md:ml-0 gap-6 pb-1 pl-1 ${expanded ? "" : "overflow-y-hidden h-[270px]"}`}> - {data?.length !== 0 ? data?.sort((a,b) => a.position - b.position).map((item, index) => { + {resources?.length !== 0 ? resources?.sort((a,b) => a.position - b.position).map((item, index) => { return ( <Cards collectionSource={collectionId} key={index} - id={item['collectionable']['id']} - title={item["collectionable"]["name"]} - author={item["collectionable"]["publisher"]["name"]} - authorId={item["collectionable"]["publisher"]["id"]} - avatar={item["collectionable"]["publisher"]["avatar"]} - image={item["collectionable"]["thumbnail"]} - updated_at={item["collectionable"]["updated_at"]} + id={item.resources.id} + title={item.resources.name} + author={item.resources.author} + authorId={item.resources.user_id} + avatar={`https://s3.c3sl.ufpr.br/mecredteste/avatar/${item.resources.user_id}`} + image={`https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${item.resources.id}`} + updated_at={item.resources.updated_at} /> ); }) diff --git a/src/app/components/Header.js b/src/app/components/Header.js index 65776f76b33f5ebe42a1e07a8675f4dda720e73f..46945961998c4949da7b214738741f145b48ee2a 100644 --- a/src/app/components/Header.js +++ b/src/app/components/Header.js @@ -1,15 +1,13 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { useState } from "react"; import Link from "next/link"; import CloseIcon from "@mui/icons-material/Close"; -import IconButton from "@mui/material/IconButton"; -import MenuIcon from "@mui/icons-material/Menu"; import SearchIcon from "@mui/icons-material/Search"; import AccountMenu from "./MenuProfile"; import SearchComponent from "./SearchComponent"; import NeedLoginModal from "./needLoginModal"; -import { isLoggedIn, useLoginBarrier } from "@/app/handlers/loginHandler"; +import { useLoggedIn, useLoginBarrier } from "@/app/handlers/loginHandler"; import { usePathname } from "next/navigation"; import { useRouter } from "next/navigation"; import { ImArrowLeft } from "react-icons/im"; @@ -19,21 +17,15 @@ import ContrastTwoToneIcon from '@mui/icons-material/ContrastTwoTone'; function DefaultContent({ handleToggleMobileSearch, - setNeedLoginOpen, - setFilterState, - filterState + setNeedLoginOpen }) { const pathname = usePathname(); const router = useRouter(); const loginBarrier = useLoginBarrier(); - const [loggedIn, setLoggedIn] = useState(false); - - useEffect(() => { - setLoggedIn(isLoggedIn()); - }, []) + const loggedIn = useLoggedIn(); const handleOpenSubmit = () => { - if (!isLoggedIn()) { + if (!loggedIn) { setNeedLoginOpen(true); } else { const params = new URLSearchParams(); @@ -47,10 +39,6 @@ function DefaultContent({ <div className="grid xl:grid-cols-[150px_auto_500px] md:grid-cols-[150px_auto_300px] grid-cols-[170px_auto] max-md:pl-8 w-full"> <Link href="/" className="flex justify-center items-center"> <div className="flex gap-2 justify-center items-center"> - {/* <div - className="w-[59px] h-[50px] bg-no-repeat bg-center bg-contain bg-logo-square" - aria-label="logo" - /> */} <img className="w-[59px] h-[50px] bg-no-repeat bg-center bg-contain bg-logo-square invertLogo-HC-white" src={"/mecred_square.svg"} @@ -63,7 +51,7 @@ function DefaultContent({ </Link> <div className="grow hidden md:flex items-center"> - <SearchComponent setFilterState={setFilterState} filterState={filterState} /> + <SearchComponent /> </div> <div className="flex justify-start shrink-0 grow-0 items-center px-[25px]"> <div className="flex items-center max-md:hidden gap-6"> @@ -85,7 +73,7 @@ function DefaultContent({ <ImArrowLeft className="rotate-90 group-hover:-rotate-180 transition max-md:mr-3 max-xl:mr-0 mr-3 mb-0.5 text-2xl" /> - <div className="max-xl:hidden max-md:flex ">Publicar recurso</div> + <div className="max-xl:hidden max-md:flex"> Publicar recurso </div> </button> </div> @@ -100,7 +88,7 @@ function DefaultContent({ <div className="max-md:hidden"><AccountMenu /></div> </> - ) : ( + ) : ( <button type="button" className="group bg-orange-HC-white hover:bg-darkOrange-HC-dark px-4 h-10 mr-8 max-sm:ml-2 rounded text-white-HC-dark-underline flex items-center font-bold flex-shrink-0 hover:text-white-HC-underline outline outline-1 outline-ice-HC-white" @@ -108,7 +96,7 @@ function DefaultContent({ > Entrar </button> - )} + )} </div> </div> @@ -117,15 +105,15 @@ function DefaultContent({ ); } -export function MobileSearch({ setFilterState, filterState, handleToggleMobileSearch }) { +export function MobileSearch({ handleToggleMobileSearch }) { return ( <div className="flex w-full items-center"> - <SearchComponent setFilterState={setFilterState} filterState={filterState} /> + <SearchComponent /> <button type="button" alt="Fechar busca" title="Fechar busca" - className={"bg-darkGray-HC-white w-10 h-10 mr-2 rounded text-white-HC-dark-underline shrink-0 transition-all duration-30000 hover:bg-turquoise-HC-dark"} + className={"bg-darkGray-HC-white w-10 h-10 mr-2 rounded text-white-HC-dark-underline shrink-0 transition-all duration-30000 hover:bg-turquoise-HC-dark"} onClick={handleToggleMobileSearch} > <CloseIcon className="h-full text-3xl " /> @@ -139,7 +127,7 @@ export function MobileSearch({ setFilterState, filterState, handleToggleMobileSe * @param {Function} props.setQuery - seta o query * @returns header */ -export default function Header({ setFilterState, filterState, mobileSearch }) { +export default function Header({ mobileSearch }) { const [needLoginOpen, setNeedLoginOpen] = useState(false); const [mobileSearchOpen, setMobileSearchOpen] = useState(false); @@ -162,14 +150,12 @@ export default function Header({ setFilterState, filterState, mobileSearch }) { return ( <div className="fixed flex items-center max-md:h-[70px] h-[120px] top-[35px] max-md:top-[0px] z-50 shadow-none w-screen bg-fundo bg-repeat bg-fixed"> <div className="flex flex-grow gap-3 sm:gap-12 justify-between "> - {mobileSearchOpen ? ( - <MobileSearch handleToggleMobileSearch={handleToggleMobileSearch} setFilterState={setFilterState} filterState={filterState} /> + {mobileSearchOpen || mobileSearch.searchIsClicked ? ( + <MobileSearch handleToggleMobileSearch={handleToggleMobileSearch}/> ) : ( <DefaultContent handleToggleMobileSearch={handleToggleMobileSearch} setNeedLoginOpen={setNeedLoginOpen} - setFilterState={setFilterState} - filterState={filterState} /> )} </div> diff --git a/src/app/components/ImageCropper.js b/src/app/components/ImageCropper.js index 9ab84a038a326f5ebb71caff8468ad59a53bdea9..5c0efe75c80292fe98f91b85109f4742c7cadb22 100644 --- a/src/app/components/ImageCropper.js +++ b/src/app/components/ImageCropper.js @@ -4,181 +4,236 @@ import "react-image-crop/dist/ReactCrop.css"; import { canvasPreview } from "./canvasPreview"; import { getStoredValue, saveToLocalStorage } from "@/app/handlers/localStorageHandler"; import mecredApi from "@/axiosConfig"; +import { Modal } from "@mui/material"; +import { authHeaders } from "../handlers/loginHandler"; +import { useRouter } from "next/navigation" -export default function ImageCropper({ payloadHeader, type, userId, setChangePhoto }) { - // Estados para gerenciar a foto original, URL da foto, configuração do corte, corte completo e imagem cortada - const [photo, setPhoto] = useState(null); - const [photoURL, setPhotoURL] = useState(null); - const [crop, setCrop] = useState(null); - const [completedCrop, setCompletedCrop] = useState(false); - const [croppedImage, setCroppedImage] = useState(null); +export default function ImageCropper({ userId, setChangePhoto }) { + // Estados para gerenciar a foto original, URL da foto, configuração do corte, corte completo e imagem cortada + const [photo, setPhoto] = useState(null); + const [photoURL, setPhotoURL] = useState(null); + const [crop, setCrop] = useState(null); + const [completedCrop, setCompletedCrop] = useState(false); + const [croppedImage, setCroppedImage] = useState(null); + const [sucessOpen, setSucessOpen] = useState(false) + const [notSucessOpen, setNotSucessOpen] = useState(false) + const router = useRouter() - // Referências para a imagem e o canvas de pré-visualização - const imgRef = useRef(null); - const previewCanvasRef = useRef(null); + // Referências para a imagem e o canvas de pré-visualização + const imgRef = useRef(null); + const previewCanvasRef = useRef(null); + // Hook personalizado para aplicar debounce em efeitos + function useDebounceEffect(fn, waitTime, deps = []) { + useEffect(() => { + const t = setTimeout(() => { + fn.apply(undefined, deps); + }, waitTime); - // Recupera tokens de autenticação do armazenamento local - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); + return () => { + clearTimeout(t); + }; + }, deps); + } - // Hook personalizado para aplicar debounce em efeitos - function useDebounceEffect(fn, waitTime, deps = []) { - useEffect(() => { - const t = setTimeout(() => { - fn.apply(undefined, deps); - }, waitTime); - - return () => { - clearTimeout(t); - }; - }, deps); - } + // Função para fazer upload da foto cortada + const uploadPhoto = async () => { + try { + let payload = new FormData(); + payload.set('file', croppedImage); + payload.set('id_user', String(userId)); + payload.set('content_type', croppedImage.type); // tipo MIME do arquivo (image/png, image/jpeg etc) - // Função para fazer upload da foto cortada - const uploadPhoto = async () => { - let payload = new FormData(); - payload.set(payloadHeader, croppedImage); - - await mecredApi.put(`/${type}/${userId}`, payload, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) - .catch(error => { - setChangePhoto(false); - if (error?.response.status === 500) // TODO: alterar isso quando tivermos um novo backend, pois não deveria retornar 500 toda vez. - console.error(error); // Talvez o backend tenha falhado, mas provavelmente a foto do perfil foi alterada. - }); - - await mecredApi.get(`/users/${userId}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) - .then(res => { - let userData = JSON.parse(getStoredValue("user_data")); - userData["avatar_file_name"] = res.data.avatar; - saveToLocalStorage("user_data", JSON.stringify(userData)); - }); - }; - - // Manipulador para quando o usuário seleciona uma foto - const handlePhoto = (e) => { - e.preventDefault(); - const file = e.target.files[0]; - if (file) { - setPhoto(file); - setPhotoURL(URL.createObjectURL(file)); - } - }; + const response = await mecredApi.post(`api/s3/upload/avatar`, payload, { + headers: authHeaders() + }); - // Função para centralizar o corte com uma proporção específica - function centerAspectCrop(mediaWidth, mediaHeight, aspect) { - return centerCrop( - makeAspectCrop( - { unit: "%", width: 50 }, - aspect, - mediaWidth, - mediaHeight - ), - mediaWidth, - mediaHeight - ); - } + console.log("Foto enviada com sucesso:", response.data); + setSucessOpen(true) - // Função chamada quando a imagem é carregada - function onImageLoad(e) { - const { width, height } = e.currentTarget; - const newCrop = centerAspectCrop(width, height, 1); - setCrop(newCrop); - setCompletedCrop(convertToPixelCrop(newCrop, width, height)); + } catch (error) { + setNotSucessOpen(true) + console.error("Erro ao enviar foto:", error); + } + }; + // Manipulador para quando o usuário seleciona uma foto + const handlePhoto = (e) => { + e.preventDefault(); + const file = e.target.files[0]; + if (file) { + setPhoto(file); + setPhotoURL(URL.createObjectURL(file)); } + }; - // Efeito com debounce para gerar a pré-visualização da imagem cortada - useDebounceEffect( - () => { - if ( - completedCrop?.width && - completedCrop?.height && - imgRef.current && - previewCanvasRef.current - ) { - // Gera a pré-visualização no canvas - canvasPreview( - imgRef.current, - previewCanvasRef.current, - completedCrop, - 1, - 0 - ); - // Converte o conteúdo do canvas em um blob e atualiza o estado - previewCanvasRef.current.toBlob((blob) => { - setCroppedImage(blob); - }); - } - }, - 100, - [completedCrop] + // Função para centralizar o corte com uma proporção específica + function centerAspectCrop(mediaWidth, mediaHeight, aspect) { + return centerCrop( + makeAspectCrop( + { unit: "%", width: 50 }, + aspect, + mediaWidth, + mediaHeight + ), + mediaWidth, + mediaHeight ); + } + + // Função chamada quando a imagem é carregada + function onImageLoad(e) { + const { width, height } = e.currentTarget; + const newCrop = centerAspectCrop(width, height, 1); + setCrop(newCrop); + setCompletedCrop(convertToPixelCrop(newCrop, width, height)); + } + + // Efeito com debounce para gerar a pré-visualização da imagem cortada + useDebounceEffect( + () => { + if ( + completedCrop?.width && + completedCrop?.height && + imgRef.current && + previewCanvasRef.current + ) { + // Gera a pré-visualização no canvas + canvasPreview( + imgRef.current, + previewCanvasRef.current, + completedCrop, + 1, + 0 + ); + // Converte o conteúdo do canvas em um blob e atualiza o estado + previewCanvasRef.current.toBlob((blob) => { + console.log("meu blob: ", blob); + setCroppedImage(blob); + }); + } + }, + 100, + [completedCrop] + ); + const ModalSucess = ({ open, onClose }) => { return ( - <div className="flex flex-col items-center"> - <div className="flex space-x-4 mt-2 mb-5"> - <label className="bg-turquoise text-white-HC-dark-underline rounded-lg p-2 cursor-pointer"> - <input type="file" onChange={handlePhoto} className="hidden" /> - Selecionar Nova Foto - </label> - {completedCrop && ( - <button - className="text-sm p-2 text-white-HC-dark-underline border-main rounded-lg font-bold bg-turquoise hover:bg-turquoise-hover" - onClick={uploadPhoto} - > - Enviar - </button> - )} + <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário + }, + }, + }}> + <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> + <p className="text-xl justify-center flex text-darkGray-HC-white-underline mb-2">Foto editada com sucesso!</p> + <div className="flex flex-row mt-2"> + + <button + className=" text-sm p-2 mr-1 text-white-HC-dark-underline border-main rounded-lg normal-case h-9 font-bold bg-turquoise hover:bg-turquoise-hover" + onClick={() => { + setChangePhoto(false); + router.push(`/perfil/${userId}`); + }} + > + Voltar para perfil + </button> + <button + className=" text-sm p-2 ml-1 text-darkGray-HC-white-underline border-main rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-ice-HC-dark-hover" + onClick={() => {onClose(); setChangePhoto(false)}} + > + Continuar editando + </button> + </div> + </div> + </Modal> + ) + } + + const ModalNotSucess = ({ open, onClose }) => { + return ( + <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário + }, + }, + }}> + <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> + <p className="text-xl justify-center flex text-darkGray-HC-white-underline mb-2">Não foi possível alterar a foto</p> + <div className="flex flex-row mt-2"> + + <button + className=" text-sm p-2 mr-1 text-white-HC-dark-underline border-main rounded-lg normal-case h-9 font-bold bg-turquoise hover:bg-turquoise-hover" + onClick={() => { + setChangePhoto(false); + router.push(`/perfil/${userId}`); + }} + > + Voltar para perfil + </button> + <button + className=" text-sm p-2 ml-1 text-darkGray-HC-white-underline border-main rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-ice-HC-dark-hover" + onClick={() => {onClose(); setChangePhoto(false)}} + > + Continuar editando + </button> + </div> + </div> + </Modal> + ) + } + + return ( + <div className="flex flex-col items-center"> + <ModalNotSucess open={notSucessOpen} onClose={() => { setNotSucessOpen(false) }} /> + <ModalSucess open={sucessOpen} onClose={() => { setSucessOpen(false) }} /> + <div className="flex space-x-4 mt-2 mb-5"> + <label className="bg-turquoise text-white-HC-dark-underline rounded-lg p-2 cursor-pointer"> + <input type="file" onChange={handlePhoto} className="hidden" /> + Selecionar Nova Foto + </label> + {completedCrop && ( + <button + className="text-sm p-2 text-white-HC-dark-underline border-main rounded-lg font-bold bg-turquoise hover:bg-turquoise-hover" + onClick={uploadPhoto} + > + Enviar + </button> + )} + </div> + {photoURL && ( + <div className="flex space-x-4"> + <ReactCrop + crop={crop} + onChange={(_, percentCrop) => setCrop(percentCrop)} + onComplete={(c) => setCompletedCrop(c)} + aspect={1} + circularCrop={true} + > + <img + ref={imgRef} + alt="Imagem de Perfil" + src={photoURL} + onLoad={onImageLoad} + style={{ maxWidth: '400px', maxHeight: '400px' }} + /> + </ReactCrop> + {completedCrop && ( + <div className="flex justify-center items-center"> + <canvas + ref={previewCanvasRef} + style={{ + borderRadius: "50%", + border: "2px solid black", + objectFit: "contain", + width: Math.min(completedCrop.width, 200), + height: Math.min(completedCrop.height, 200), + }} + /> </div> - {photoURL && ( - <div className="flex space-x-4"> - <ReactCrop - crop={crop} - onChange={(_, percentCrop) => setCrop(percentCrop)} - onComplete={(c) => setCompletedCrop(c)} - aspect={1} - circularCrop={true} - > - <img - ref={imgRef} - alt="Imagem de Perfil" - src={photoURL} - onLoad={onImageLoad} - style={{ maxWidth: '400px', maxHeight: '400px' }} - /> - </ReactCrop> - {completedCrop && ( - <div className="flex justify-center items-center"> - <canvas - ref={previewCanvasRef} - style={{ - borderRadius: "50%", - border: "2px solid black", - objectFit: "contain", - width: Math.min(completedCrop.width, 200), - height: Math.min(completedCrop.height, 200), - }} - /> - </div> - )} - </div> - )} + )} </div> - ); + )} + </div> + ); } diff --git a/src/app/components/InfiniteScroll.js b/src/app/components/InfiniteScroll.js index 1f1fa624fe19a16844c31cc1f3408ca979197307..7d732ed1dce6615e0872467d1d0b54ba578c065a 100644 --- a/src/app/components/InfiniteScroll.js +++ b/src/app/components/InfiniteScroll.js @@ -2,129 +2,124 @@ import React, { useState, useEffect, useCallback } from "react"; import InfiniteScrollCards from "./InfiniteScrollCards"; import mecredApi from "@/axiosConfig"; import Loading from "./Loading"; -import { useSearchParams, useRouter } from "next/navigation"; const getUrlFromFilterState = (filterState, page) => { const apiParams = new URLSearchParams(); apiParams.append("page", page); - apiParams.append("results_per_page", "20"); - apiParams.append("order", filterState.order); + apiParams.append("page_size", "20"); + apiParams.append("sortBy", filterState.order); apiParams.append("query", filterState.query); - apiParams.append("search_class", filterState.searchClass); - apiParams.append("subjects[]", filterState.subjects.map((obj) => obj.id).toString()); - apiParams.append("object_types[]", filterState.objectTypes.map((obj) => obj.id).toString()); - apiParams.append("educational_stages[]", filterState.educationalStages.map((obj) => obj.id).toString()); - apiParams.append("languages[]", filterState.languages.map((obj) => obj.id).toString()); + apiParams.append("index", filterState.searchClass); + apiParams.append("subjects", filterState.subjects.map((obj) => obj.name)) + apiParams.append("objectType", filterState.objectTypes.map((obj) => obj.name)); + apiParams.append("educational_stages", filterState.educationalStages.map((obj) => obj.name)); + apiParams.append("language", filterState.languages.map((obj) => obj.name)); - return `/search?${apiParams.toString()}`; + return `public/elastic/search?${apiParams.toString()}`; } - /** * @param {Object} props - * @param {String} props.filterSubject matérias selecionadas - string de números inteiros, cada um correspondendo a uma máteria - * @param {String} props.query query digitanda na barra de busca do site - * @param {String} props.type nome em inglês da página (ex:collections) - * @param {String} props.filter nome do filtro selecionado (ex:publicationdesc) - * @param {Function} props.setNewSize - * @param {Boolean} props.newSize boolean que verifica se o tamanho da tela foi alterado - * @returns faz a chamada dos recursos/coleções conforme a tela é scrollada ou os filtros alterados + * @param {Object} props.filterState Estado atual dos filtros + * @param {Function} props.setNewSize Atualizador para mudanças de layout + * @param {Boolean} props.newSize Indica se o tamanho da tela mudou + * @param {Function} props.setItems Função para atualizar os itens renderizados + * @param {Array} props.items Itens renderizados + * @param {AbortController} props.abortController Controlador de abort para chamadas fetch */ export default function InfiniteScroll({ filterState, setNewSize, newSize, setItems, items, abortController }) { const [isLoading, setIsLoading] = useState(false); const [mecLoading, setMecLoading] = useState(true); const [page, setPage] = useState(0); - const [isScroll, setIsScroll] = useState(false) - const router = useRouter(); - - // começa com 1 pq quando carrega a página a primeira vez, caso zero, a mensagem aparece por alguns segundos - const [totalCount, setTotalCount] = useState(1) - - const fetchData = useCallback(async (page) => { + const [isScroll, setIsScroll] = useState(false); + const [totalCount, setTotalCount] = useState(1); + // função para buscar dados (sem useCallback) + const fetchData = async (page, filterStateSnapshot) => { setIsLoading(true); + const url = getUrlFromFilterState(filterStateSnapshot, page); + console.log("url aqui:", url); - const url = getUrlFromFilterState(filterState, page); try { const { data, headers } = await mecredApi.get(url, { - signal: abortController.signal + signal: abortController.signal, }); - // verificando se a conteudo para a url passada, caso nao tenha emite mensagem de conteudo nao enconttrado - setTotalCount(Number(headers["x-total-count"])) - setItems((prevItems) => { - // TODO: O backend retorna itens repetidos porque ordena as páginas por - // um atributo que pode repetir. A solução adequada é que o backend ordene - // por mais de um campo, por exemplo pelo filtro passado e depois pelo id, - // assim as ordenações serão estáveis. - // Solução hack atual: remover os itens duplicados. + console.log("Data Results:", data.results); + //AQUI: Acho que da para retirar o total count e substituir pelo page + + + console.log("headers", headers) + setTotalCount(Number(headers["x-total-count"])); + console.log("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", data.results) + + setItems( + prevItems => { const idsSeen = new Set(); const withoutDupes = []; - [...page === 0 ? [] : prevItems, ...data].forEach((item) => { - if (idsSeen.has(item.id)) { return; } - idsSeen.add(item.id); + + [...(page === 0 ? [] : prevItems), ...data.results].forEach((item) => { + if (idsSeen.has(item._id)) return; + idsSeen.add(item._id); withoutDupes.push(item); }); - return withoutDupes; - }); + + return withoutDupes; + } + ); setPage(() => page + 1); } catch (error) { console.error(error); } finally { setIsLoading(false); - setIsScroll(false) + setIsScroll(false); } - }, [filterState, setItems]); + }; - //verifica se o usuário chegou no final da página + // disparado quando o usuário rola até o fim da tela const handleScroll = useCallback(() => { - // Verifica se o usuário está próximo do final da área rolável if ( - window.innerHeight * 4 + window.scrollY < document.documentElement.offsetHeight || + window.innerHeight * 4 + window.scrollY < document.documentElement.offsetHeight || isLoading ) { return; } - - // Busca mais dados - // Só faz a requisição se não for na página do MEC - !(filterState.searchClass == "MEC") && fetchData(page); - }, [fetchData, isLoading, page]); - - //caso o filtro seja alterado, seta o site para o topo da tela + + fetchData(page, filterState); + }, [isLoading, page, filterState]); + + // recarrega os dados ao alterar os filtros useEffect(() => { - fetchData(0).then(() => { window.scrollTo(0, 0) }); + setPage(0); + fetchData(0, filterState).then(() => window.scrollTo(0, 0)); setIsScroll(true); - }, [fetchData]); + }, [filterState]); - //controle do scroll da página + // escuta o scroll da janela useEffect(() => { window.addEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll); }, [isLoading, handleScroll]); return ( <> - + {items && console.log("itens", items)} <div className={`${isScroll ? " blur-sm" : ""}`}> - {totalCount ? - - <InfiniteScrollCards setMecLoading={setMecLoading} setNewSize={setNewSize} newSize={newSize} data={items} searchClass={filterState?.searchClass} /> - : + {page && Array.isArray(items)? ( + <InfiniteScrollCards + setNewSize={setNewSize} + newSize={newSize} + data={items} + searchClass={filterState?.searchClass} + /> + ) : ( <div className="flex justify-center text-2xl font-bold text-darkGray-HC-white mt-72 text-center"> Desculpe, não encontramos nada. <br /> {": ("} </div> - - } + )} </div> - - { filterState.searchClass == "MEC" ? (mecLoading) && <Loading scroll={false} /> : - (isLoading ) && - <Loading scroll={ isScroll} /> - - } + {isLoading && <Loading scroll={isScroll} />} </> - ); } diff --git a/src/app/components/InfiniteScrollCards.js b/src/app/components/InfiniteScrollCards.js index 33976bed7c1cb16eb18cfaba770b111d0f3756f2..660db45cfb7a9721bea9db6063e0739ff8c8b04e 100644 --- a/src/app/components/InfiniteScrollCards.js +++ b/src/app/components/InfiniteScrollCards.js @@ -41,14 +41,14 @@ export default function InfiniteScrollCards({ setMecLoading, data, searchClass, */ useEffect(() => { const fetchCollections = async () => { - try { - const { data } = await mecredApi - .get("/users/35342/collections"); - setMecCollection([...data].reverse()); - setMecLoading(false); - } catch (error) { - console.error(error); - } + // comentado pq nao tem coleções do mec no teste + // try { + // const { data } = await mecredApi + // .get("/users/35342/collections"); + // setMecCollection([...data].reverse()); + // } catch (error) { + // console.error(error); + // } }; fetchCollections(); @@ -77,7 +77,7 @@ export default function InfiniteScrollCards({ setMecLoading, data, searchClass, function returnContent(type) { switch (type) { - case "Collection": + case "collections": return ( <div className="justify-center ml-8"> {data?.map((item) => ( @@ -125,20 +125,20 @@ export default function InfiniteScrollCards({ setMecLoading, data, searchClass, ); - case "LearningObject": + case "resources": return ( <div className="flex justify-around max-2xl:gap-4 gap-2 gap-y-4 flex-wrap ml-7 max-md:ml-0 max-md:justify-center"> {data?.map((item, index) => ( - <Cards - id={item['id']} - key={item['id']} - title={item["name"]} - author={item["publisher"]["name"]} - authorId={item["publisher"]["id"]} + <Cards + id={item['_id']} + key={item['_id']} + title={item["_source"]["name"]} + author={item["_source"]["author"]} + authorId={item["_source"]["user_id"]} avatar={item["publisher"]["avatar"]} image={item["thumbnail"]} - type={item["object_type"]} - updated_at={item["created_at"]} + type={item["_source"]["objectType"]} + updated_at={item["_source"]["created_at"]} /> ))} </div> @@ -187,7 +187,7 @@ export default function InfiniteScrollCards({ setMecLoading, data, searchClass, </div> ); - case "User": + case "users": return ( <div className="flex flex-wrap justify-center"> {data?.map((item, i) => { diff --git a/src/app/components/InfiniteScrollResults.js b/src/app/components/InfiniteScrollResults.js new file mode 100644 index 0000000000000000000000000000000000000000..e717adc14a6a9dd270b8facd6d9c4d3f116e9818 --- /dev/null +++ b/src/app/components/InfiniteScrollResults.js @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState, useCallback } from "react" +import CardsLibrary from "./CardsLibrary" +import mecredApi from "@/axiosConfig" + +export default function InfiniteScrollResults({ filters, currentCategory }) { + const [results, setResults] = useState([]) + const [page, setPage] = useState(0) + const [hasMore, setHasMore] = useState(true) + const [loading, setLoading] = useState(false) + + const observer = useRef() + const abortControllerRef = useRef(null) + + const getUrlFromFilter = (filter, currentCategory, page) => { + const apiParams = new URLSearchParams() + + apiParams.append("index", filter.entidade) + if (filter.pesquisa) apiParams.append("query", filter.pesquisa) + + if (filter.formato?.length) { + filter.formato.forEach(f => apiParams.append('object_type', f)) + } + + if (filter.nivel?.length) { + filter.nivel.forEach(n => apiParams.append('educational_stages', n)) + } + + if (filter.idioma?.length) { + filter.idioma.forEach(i => apiParams.append('languages', i)) + } + + if (filter.materias?.length) { + filter.materias.forEach(m => apiParams.append('subjects', m)) + } + + apiParams.append("state", "accepted") + apiParams.append("sort_by", currentCategory) + apiParams.append("page_size", 20) + apiParams.append("offset", page ) + + return `public/elastic/search?${apiParams.toString()}` + } + + const fetchData = async (signal) => { + if (loading || !hasMore) return + setLoading(true) + + const url = getUrlFromFilter(filters, currentCategory, page) + + try { + const { data } = await mecredApi.get(url, { signal }) + + const items = data.results + if (!items.length) { + setHasMore(false) + } else { + setResults(prev => [...prev, ...items]) + setPage(prev => prev + 1) + } + } catch (err) { + if (err.name === "CanceledError") { + console.log("requisição cancelada") + } else { + console.error("erro ao buscar dados", err) + setHasMore(false) + } + } finally { + setLoading(false) + } + } + + const lastItemRef = useCallback(node => { + if (loading) return + if (observer.current) observer.current.disconnect() + + observer.current = new IntersectionObserver(entries => { + if (entries[0].isIntersecting && hasMore) { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + const newController = new AbortController() + abortControllerRef.current = newController + fetchData(newController.signal) + } + }) + if (node) observer.current.observe(node) + }, [hasMore, loading, filters, currentCategory, page]) + + useEffect(() => { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + const newController = new AbortController() + abortControllerRef.current = newController + + setResults([]) + setPage(0) + setHasMore(true) + setLoading(false) + + fetchData(newController.signal) + + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort() + } + } + }, [filters, currentCategory]) + + return ( + <div className="w-screen mx-auto px-4 mt-28 overflow-auto"> + {results.length > 0 && <CardsLibrary page="Recursos" data={results} />} + <div ref={lastItemRef} className="h-10 mt-10 flex justify-center items-center text-gray-500"> + {loading && "carregando..."} + {!hasMore && "fim dos resultados"} + </div> + </div> + ) +} diff --git a/src/app/components/ItemNotification.js b/src/app/components/ItemNotification.js index 78155715dcdd8349c3e7f4eb1e03ceac33dfadd0..622e2ed414201b89088b3ba4c98743fdd73563a7 100644 --- a/src/app/components/ItemNotification.js +++ b/src/app/components/ItemNotification.js @@ -1,7 +1,8 @@ -import { mecredURL } from "@/axiosConfig"; +import mecredApi, { mecredURL } from "@/axiosConfig"; import Image from "next/image"; import { useRouter } from "next/navigation"; - +import { useEffect, useState } from "react"; +import { authHeaders } from "../handlers/loginHandler"; const timeFunction = (updated_time) => { let data = new Date(updated_time) @@ -50,7 +51,7 @@ function tradutor(nome) { case "curator_assignment.create": return "você foi atribuido para avaliar " case "submission.rejected": - return "sua submissão foi rejeitada pela equipe de avaliadores. " + return "sua submissão foi rejeitada pela equipe de avaliadores. " case "submission.accepted": return "sua submissão foi aceita pela equipe de avaliadores: " @@ -71,28 +72,58 @@ export default function ItemNotification({ notification, postViewNotification }) "ids": [notification.id] } } + + + postViewNotification(payload) + router.push(`/recurso/${notification.recipient?.id}`) postViewNotification(payload) router.push(`${baseUrl}/recurso/${notification.recipient?.id}`) } + const [imageExists, setImageExists] = useState(false); + const imageUrl = `https://s3.c3sl.ufpr.br/mecredteste/avatar/${notification.owner_id}`; + + useEffect(() => { + const checkImage = async () => { + await mecredApi.get(`public/s3/get/avatar/${notification.owner_id}`, { + headers: authHeaders() + }) + .then(({ headers }) => setImageExists(true)) + .catch((error) => { + setImageExists(false) + }) + }; + + checkImage(); + }, [notification.owner_id]); return ( <div className="flex flex-row px-3 py-4 mt-1 ml-2 hover:bg-ice-HC-dark hover:rounded-lg" onClick={viewOneNotification} > <div className="pr-3 pl-1 shrink-0 flex items-center"> + {!imageExists ? <div className={`flex items-center justify-center text-xl font-bold text-ice-HC-dark rounded-full h-[43px] w-[43px] ${getRandomBg(notification.owner_id)}`} >{notification.owner_name[0]}</div> + : + <Image + src={imageUrl} + alt={notification.owner_name} + width={43} + height={43} + className="w-[43px] h-[43px] object-cover rounded-full" + /> + } </div> <div> <p className="text-sm font-bold text-darkGray-HC-white line-clamp-1"> - {notification.owner.name} <br /> + {notification.actor_name} </p> - <p className="text-sm font-normal text-darkGray-HC-white line-clamp-3 "> - {tradutor(notification.activity)} - <a className=" text-sm text-turquoise-HC-underline -hover " href={`${baseUrl}/recurso/${notification.recipient?.id}`} > + <p className="text-sm font-normal text-darkGray-HC-white line-clamp-2 "> + {tradutor(notification.action_name)} + <a className="text-turquoise-HC-underline -hover" href={`/recurso/${notification.recipient?.id}`} > {notification.recipient?.name} </a> </p> <p className="text-sm font-light text-darkGray-HC-white "> - {timeFunction(notification.created_at)} + {timeFunction(notification.createdAt)} </p> </div> </div> diff --git a/src/app/components/MenuProfile.js b/src/app/components/MenuProfile.js index 1e03cd29df1d00077ff5ddb06afdc3db7a92d27e..c191ca2c06df271785ec7416c828702e9bfb368d 100644 --- a/src/app/components/MenuProfile.js +++ b/src/app/components/MenuProfile.js @@ -11,7 +11,7 @@ import { getStoredValue, removeFromLocalStorage, } from "../handlers/localStorageHandler"; -import { isLoggedIn } from "../handlers/loginHandler"; +import { isLoggedIn, logOut, useLoggedIn, userData } from "../handlers/loginHandler"; import AccountCircleRoundedIcon from '@mui/icons-material/AccountCircleRounded'; import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded'; import PrivacyTipRoundedIcon from '@mui/icons-material/PrivacyTipRounded'; @@ -76,25 +76,17 @@ function getRandomBg(id) { */ export default function AccountMenu() { const [anchorEl, setAnchorEl] = useState(null); - const [userData, setUserData] = useState({}); + const [dataUser, setDataUser] = useState({}); const [id, setId] = useState(null); - const [logged, setLogged] = useState(false); const open = Boolean(anchorEl); - - const router = useRouter(); + const loggedIn = useLoggedIn(); useEffect(() => { - if (isLoggedIn()) { - let data = JSON.parse(getStoredValue("user_data")); - setId(data["id"]); - setLogged(isLoggedIn()); - } - let token = getStoredValue("access_token"); - if (token) { - let user = getStoredValue("user_data"); - setUserData(JSON.parse(user)); + if (loggedIn) { + let data = userData(); + setId(data?.user?.id); } - }, []); + }, [loggedIn]); const handleClick = (event) => { setAnchorEl(event.currentTarget); @@ -105,12 +97,7 @@ export default function AccountMenu() { }; const handleLogout = () => { - removeFromLocalStorage("access_token"); - removeFromLocalStorage("user_data"); - removeFromLocalStorage("uid"); - removeFromLocalStorage("expiry"); - removeFromLocalStorage("token-type"); - removeFromLocalStorage("client"); + logOut(); window.location.reload(); }; @@ -127,13 +114,13 @@ export default function AccountMenu() { title="Meu Perfil" alt="Acessar meu perfil" > - {(userData["avatar_file_name"] === "") || (userData["avatar_file_name"] === null) ? ( - <div className={`flex items-center max-sm:-ml-px justify-center text-xl font-bold text-ice-HC-dark bg-turquoise-HC-white rounded-full h-[38px] w-[38px] ${getRandomBg(userData["id"])}`} >{userData["name"][0]}</div> + {(dataUser["avatar_file_name"] === "") || (dataUser["avatar_file_name"] === null) ? ( + <div className={`flex items-center max-sm:-ml-px justify-center text-xl font-bold text-ice-HC-dark bg-turquoise-HC-white rounded-full h-[38px] w-[38px] ${getRandomBg(dataUser["id"])}`} >{dataUser["name"][0]}</div> ) : ( <Avatar src={ mecredURL + - userData["avatar_file_name"] + dataUser["avatar_file_name"] } sx={{ width: 38, height: 38 }} /> @@ -168,20 +155,20 @@ export default function AccountMenu() { }} > <MenuItem className="ml-1" sx={{ 'pointer-events': 'none' }} > - {(userData["avatar_file_name"] === "") || (userData["avatar_file_name"] === null) ? ( - <div className={`flex items-center justify-center pt-1 text-xl font-bold text-ice-HC-dark bg-turquoise-HC-white rounded-full h-[38px] w-[38px] ${getRandomBg(userData["id"])}`} >{userData["name"][0]}</div> + {(dataUser["avatar_file_name"] === "") || (dataUser["avatar_file_name"] === null) ? ( + <div className={`flex items-center justify-center pt-1 text-xl font-bold text-ice-HC-dark bg-turquoise-HC-white rounded-full h-[38px] w-[38px] ${getRandomBg(dataUser["id"])}`} >{dataUser["name"][0]}</div> ) : ( <Avatar src={ mecredURL + - userData["avatar_file_name"] + dataUser["avatar_file_name"] } sx={{ width: 38, height: 38 }} /> )} <div className="grid grid-rows-2 text-darkGray-HC-white ml-4 mt-2 grid-flow-row"> - <p className="row-span-1"> {userData["name"]} </p> - <p className="row-span-2 text-xs"> {userData["uid"]} </p> + <p className="row-span-1"> {dataUser["name"]} </p> + <p className="row-span-2 text-xs"> {dataUser["uid"]} </p> </div> </MenuItem> <hr className="border-mediumGray-HC-white h-1 my-2 "/> diff --git a/src/app/components/ModalNotifications.js b/src/app/components/ModalNotifications.js index 0704ca55a840f02dfc19105d64c9f03569c3cee6..1a9e71625988fe86b1e8cf201031068662d217ad 100644 --- a/src/app/components/ModalNotifications.js +++ b/src/app/components/ModalNotifications.js @@ -8,10 +8,6 @@ import Image from 'next/image'; import Loading from './Loading'; import ItemNotification from './ItemNotification'; - - - - export default function ModalNotifications({ countNotifications, notifications, postViewNotification }) { //if (notifications?.activity === "complaint.create") return null; const [anchorEl, setAnchorEl] = useState(null); @@ -25,16 +21,19 @@ export default function ModalNotifications({ countNotifications, notifications, const setViewdAllNotification = () => { const payload = { - "activities": { - "ids": [] + activities: { + ids: notifications.map((noti) => noti.id), + viewed: true // <-- necessário pro Zod validar! } - } + }; notifications.forEach((noti) => payload.activities.ids.push(noti.id)); - + + console.log(payload) + postViewNotification(payload) } - + return ( <div> <Button @@ -48,15 +47,15 @@ export default function ModalNotifications({ countNotifications, notifications, aria-expanded={open ? 'true' : undefined} onClick={handleClick} > - <Badge badgeContent={countNotifications} - + <Badge badgeContent={countNotifications} + sx={{ '& .MuiBadge-badge': { backgroundColor: 'var(--turquoise-HC-white)', color: 'var(--ice-HC-dark)' } }} - > + > <NotificationsIcon className="h-full text-3xl" /> </Badge> @@ -66,7 +65,7 @@ export default function ModalNotifications({ countNotifications, notifications, anchorEl={anchorEl} open={open} onClose={handleClose} - sx={{ + sx={{ top: 20, backgroundColor: 'transparent', '& .MuiPaper-root': { @@ -93,7 +92,7 @@ export default function ModalNotifications({ countNotifications, notifications, <div key={index} className='pl-2' > <ItemNotification notification={notification} postViewNotification={postViewNotification} /> <div className='pl-4'> - <hr className="border-mediumGray-HC-white h-1"/> + <hr className="border-mediumGray-HC-white h-1" /> </div> </div> diff --git a/src/app/components/NavigationBar.js b/src/app/components/NavigationBar.js index 9e9dd314a9e2c79e90fe5955bbf1530ecf3965b0..f0c69fc62397fff2204bec15b3697874fb327611 100644 --- a/src/app/components/NavigationBar.js +++ b/src/app/components/NavigationBar.js @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; import { usePathname, useSearchParams } from "next/navigation"; import NeedLoginModal from "./needLoginModal"; -import { isLoggedIn } from "../handlers/loginHandler"; +import { isLoggedIn, logOut } from "../handlers/loginHandler"; import { getStoredValue, removeFromLocalStorage, @@ -54,18 +54,12 @@ export default function NavigationBar({ mobileSearch }) { if (userData) { const data = JSON.parse(userData); setId(data["id"]); - setLoggedIn(true) } } }, []); const handleLogout = () => { - removeFromLocalStorage("access_token"); - removeFromLocalStorage("user_data"); - removeFromLocalStorage("uid"); - removeFromLocalStorage("expiry"); - removeFromLocalStorage("token-type"); - removeFromLocalStorage("client"); + logOut(); window.location.reload(); }; @@ -85,6 +79,17 @@ export default function NavigationBar({ mobileSearch }) { if (href === "#") return "#"; return `/busca?page=${href}`; }; + const navItems = [ + { label: "Pesquisar", href: "#", icon: SearchIcon }, + { label: "Perfil", href: "/perfil", icon: Person }, + { label: "Coleções", href: "/busca?page=Collection", icon: CollectionsBookmarkIcon }, + { label: "Recursos", href: "/busca?page=LearningObject", icon: SubjectIcon }, + { label: "Sobre", href: "/sobre", icon: HelpIcon }, + { label: "MEC", href: "/busca?page=MEC", icon: VerifiedIcon }, + { label: "Publicar", href: "/publicar", icon: FileUploadIcon }, + { label: "Contato", href: "/contato", icon: EmailRoundedIcon }, + { label: "Sair", href: "", icon: LogoutRounded}, + ] return ( <> @@ -92,9 +97,7 @@ export default function NavigationBar({ mobileSearch }) { <nav className="bg-lightGray-HC-dark h-50 outline outline-1 outline-ice-HC-white text-darkGray-HC-white-underline font-light fixed bottom-0 left-0 w-full z-10"> <ul className="flex justify-between overflow-x-auto no-scrollbar animate-scrollHint"> - {navItems - .filter(item => loggedIn || (item.label !== "Sair" && item.href !== "/perfil")) - .map((item, index) => { + {navItems.map((item, index) => { const isActive = page === item.href || pathname === item.href || diff --git a/src/app/components/Notifications.js b/src/app/components/Notifications.js index c8d8cd9c8d582863b836fbbd719ff2cf50aad2e1..83aa18451e265a6f3567418ee74c35fb70582761 100644 --- a/src/app/components/Notifications.js +++ b/src/app/components/Notifications.js @@ -1,37 +1,25 @@ import mecredApi from "@/axiosConfig"; -import { useLoginBarrier } from "@/app/handlers/loginHandler"; +import { authHeaders, useLoggedIn, useLoginBarrier } from "@/app/handlers/loginHandler"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import { useEffect, useState } from 'react' import ModalNotifications from "./ModalNotifications"; -export default function Notifications() { +export default function Notifications({id}) { const [notifications, setNotifications] = useState([]); const [countNotifications, setCountNotifications] = useState(null); const loginBarrier = useLoginBarrier() - - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - + const loggedIn = useLoggedIn() useEffect(() => { - if (!loginBarrier()) + if (!loggedIn) return - const url = `/feed?offset=0&limit=30` - - const getNotifications = async (url) => { + const getNotifications = async () => { await mecredApi - .get(url, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + .get(`api/notifications`, { + headers: authHeaders() }) .then(({ data }) => { - + console.log(data) // temp pq o set fica um passo atras ai nao eh possivel usar o set do countNotifications let temp = data.filter((noti) => {return((noti.viewed === false && noti.activity != "complaint.create"))}) setNotifications(temp) @@ -39,23 +27,21 @@ export default function Notifications() { }) .catch((error) => console.error(error)) } + // const getOwnerNotification = async () => { + // await mecredApi + // .get(`/api/user/${notifications}`) + // } //chama funcao - getNotifications(url) + getNotifications() - }, [loginBarrier, uid, client, token]) + }, [loggedIn, uid, client, token]) const postViewNotification = async (payload) => { - + console.log("oiii", payload) await mecredApi - .post("/activities/view", payload, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + .post("api/notifications/updateMany", payload, { + headers: authHeaders() }) .then(() => { let temp = notifications.filter((noti) => (!payload.activities.ids.includes(noti.id))) diff --git a/src/app/components/SearchComponent.js b/src/app/components/SearchComponent.js index 90d22296751495b0b58eb1903f62877a1921f4f7..d3808c491d7498a16648ef514e7c99b96f5ff4e0 100644 --- a/src/app/components/SearchComponent.js +++ b/src/app/components/SearchComponent.js @@ -1,15 +1,7 @@ -import { styled, alpha } from "@mui/material/styles"; import SearchIcon from "@mui/icons-material/Search"; -import KeyboardVoiceIcon from "@mui/icons-material/KeyboardVoice"; -import KeyboardIcon from "@mui/icons-material/Keyboard"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useState, useRef, useCallback } from "react"; +import { useState, useRef } from "react"; -let suggestions = [ - { name: "Recursos", class: "LearningObject", ref: "busca?page=LearningObject" }, - { name: "Coleções", class: "Collection", ref: "busca?page=Collection" }, - { name: "Usuários", class: "User", ref: "busca?page=User" }, -]; /** * @@ -18,16 +10,12 @@ let suggestions = [ * @param {Boolean} props.sizeWindow * @returns searchComponent na header */ -export default function SearchComponent({ setFilterState, filterState, sizeWindow }) { +export default function SearchComponent() { + const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); - const [input, setInput] = useState(""); - const [drop, setDrop] = useState(false); - const [width, setWidth] = useState(0); - const [selectedIndex, setSelectedIndex] = useState(0); - const dropdownRef = useRef(null); + const [input, setInput] = useState(searchParams["pesquisa"] ? searchParams["pesquisa"] : ""); const searchRef = useRef(null); - const searchParams = useSearchParams() const [isFocused, setIsFocused] = useState(false); const handleFocus = () => { @@ -41,106 +29,21 @@ export default function SearchComponent({ setFilterState, filterState, sizeWindo const handleSubmit = (e) => { e.preventDefault(); - const search = input === "" ? "*" : input; + const search = input === "" ? "" : input; - let defaultType = "/busca?page=Collection"; - - if ( - pathname === "/busca?page=Collection" || - pathname === "/busca?page=LearningObject" || - pathname === "/busca?page=User" - ) { - defaultType = pathname; - } - - if (!drop) { - router.push(`${defaultType}&filter=${search}`); - } else { - router.push(`/${suggestions[selectedIndex].ref}&filter=${search}`); - } - }; + const params = new URLSearchParams(searchParams); + params.set("pesquisa", search) + const url = pathname + '?' + params.toString(); + router.push(url); + + setIsFocused(false); - const handleClickOutside = (e) => { - if (dropdownRef.current && !dropdownRef.current.contains(e.target)) { - setDrop(false); - } }; + - const handleKeyDown = useCallback( - (e) => { - if (!drop) return; - - if (e.key === "ArrowDown") { - e.preventDefault(); - setSelectedIndex((prevIndex) => - prevIndex < suggestions.length - 1 ? prevIndex + 1 : 0 - ); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - setSelectedIndex((prevIndex) => - prevIndex > 0 ? prevIndex - 1 : suggestions.length - 1 - ); - } else if (e.key === "Enter" && selectedIndex >= 0) { - router.push(`/${suggestions[selectedIndex].ref}&filter=${input}`); - setDrop(false) - } - }, - [drop, input, router, selectedIndex] - ); - - useEffect(() => { - - if (drop) { - document.addEventListener("keydown", handleKeyDown); - } else { - document.removeEventListener("keydown", handleKeyDown); - } - - return () => { - document.removeEventListener("keydown", handleKeyDown); - }; - }, [drop, handleKeyDown]); - - useEffect(() => { - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, []); - - useEffect(() => { - if (searchRef.current) { - setWidth(searchRef.current.offsetWidth); - } - }, [input, drop]); - - useEffect(() => { - const handleResize = () => { - if (searchRef.current) { - setWidth(searchRef.current.offsetWidth); - } - }; - - window.addEventListener("resize", handleResize); - - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); - - useEffect(() => { - const index = suggestions.map(s => s.class).indexOf(searchParams.get('page')); - if (index !== -1) { // Estamos em uma página válida - setSelectedIndex(index); - } - }, [searchParams]); return ( <form - onKeyDown={(e) => { - if (e.key === "Enter") e.preventDefault(); - if (drop) e.stopPropagation(); - }} className="w-full max-sm:w-[90%] max-md:mx-2 h-[50px] z-30 items-center" onSubmit={handleSubmit} > @@ -160,15 +63,11 @@ export default function SearchComponent({ setFilterState, filterState, sizeWindo placeholder:text-darkGray-HC-dark outline-ice-HC-white align-middle h-full w-full" onFocus={handleFocus} + value={input} onBlur={handleBlur} onChange={(e) => { setInput(e.target.value); - setDrop(e.target.value !== ""); }} - onClick={(e) => - setDrop(e.target.value !== "") - } - onKeyDown={handleKeyDown} /> </div> <button @@ -180,36 +79,6 @@ export default function SearchComponent({ setFilterState, filterState, sizeWindo <SearchIcon className="h-full text-4xl max-sm:text-3xl" /> </button> </div> - {drop && ( - <div - ref={dropdownRef} - className={`fixed z-50 bg-white-HC-dark rounded-lg shadow-md `} - style={{ width: `${width}px` }} - tabIndex={0} - > - <ul className=" z-10"> - {suggestions.map((suggestion, index) => ( - <li - key={index} - onClick={() => { - router.push(`/${suggestion.ref}&filter=${input}`); - setDrop(false) - - }} - className={`p-2 text-darkGray-HC-white hover:bg-ice-HC-dark cursor-pointer flex ${ - index === selectedIndex ? "bg-ice-HC-dark " : "" - }`} - > - <div className="truncate">{input}</div>{" "} - <div className=" flex-shrink-0 indent-1"> - {" "} - em {suggestion.name} - </div> - </li> - ))} - </ul> - </div> - )} </form> ); } diff --git a/src/app/components/ShareButton.js b/src/app/components/ShareButton.js index cd538d50a707d18fcaf086b0510f5e791ee97b58..f6b43da8f2a56e0635b623f639707dde17b1c7fc 100644 --- a/src/app/components/ShareButton.js +++ b/src/app/components/ShareButton.js @@ -12,19 +12,23 @@ export default function ShareButton({ type, id }) { const [shareOpen, setShareOpen] = useState(false); const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''; - const pathAux = typeof window !== 'undefined' ? window.location.pathname : ''; - const path = pathAux.split('/')[1]; + const link = `${baseUrl}/${type}/${id}`; // Gera o link dinâmico baseado no tipo e ID return ( <> {type === "perfil" ? ( <button - className="p-2 text-sm rounded-[10px] max-md:w-40 w-44 h-11 hover:bg-darkGray-HC-white hover:text-darkGray-HC-dark outline outline-1 outline-ice-HC-white text-darkGray-HC-white-underline hover:text-white-HC-dark-underline font-bold normal-case flex justify-center items-center gap-2" - onClick={() => setShareOpen(true)} - aria-label="Compartilhar" + className="p-2 text-sm rounded-[10px] max-md:w-44 w-48 h-11 hover:bg-darkGray-HC-white hover:text-darkGray-HC-dark outline outline-1 outline-lightGray-HC-white text-darkGray-HC-white-underline hover:text-white-HC-dark-underline font-bold normal-case flex justify-center items-center gap-2" + onClick={() => setShareOpen(true)} + aria-label="Compartilhar" > - <ShareOutlinedIcon fontSize="small" /> - <span className="hidden md:inline">Compartilhar perfil</span> + <ShareOutlinedIcon fontSize="small" /> + {type === "perfil" ? + <span className="hidden md:inline">Compartilhar Perfil</span> + : + <span className="hidden md:inline">Compartilhar</span> + } + </button> ) : ( <button diff --git a/src/app/components/SideBar.js b/src/app/components/SideBar.js index 80b9b76c2ac6756bef3b6edf81a40b11de23cc01..1f2bbcf66b90852592a0929527d338c1147285f1 100644 --- a/src/app/components/SideBar.js +++ b/src/app/components/SideBar.js @@ -10,7 +10,7 @@ import VerifiedIcon from "@mui/icons-material/Verified"; import { usePathname, useSearchParams } from "next/navigation"; import { Person } from "@mui/icons-material"; import FileUploadIcon from '@mui/icons-material/FileUpload'; -import { isLoggedIn } from "../handlers/loginHandler"; +import { isLoggedIn, useLoggedIn, userData } from "../handlers/loginHandler"; import NeedLoginModal from "./needLoginModal"; import LogoutRounded from '@mui/icons-material/LogoutRounded'; import { @@ -32,16 +32,22 @@ const acessoRapido = [ id: "Perfil", }, { - title: "Coleções", + title: "MEC", + icon: VerifiedIcon, + href: "/MEC", + id: "MEC", + }, + { + title: "Biblioteca", icon: CollectionsBookmarkIcon, - href: "Collection", + href: "/biblioteca", id: "Coleções", }, { - title: "Recursos", - icon: SubjectIcon, - href: "LearningObject", - id: "Recursos", + title: "Perfil", + icon: Person, + href: "/perfil", + id: "Perfil", }, { title: "Sobre", @@ -70,50 +76,17 @@ const acessoRapido = [ { title: "Sair", icon: LogoutRounded, - href: "/entrar", - id: "Sair", + href: "/sobre", + id: "sobre", } ]; -function tradutor(name) { - switch (name) { - case "LearningObject": - return "Recursos" - case "Collection": - return "Coleções" - case "MEC": - return "MEC" - case "/perfil": - return "Perfil" - case "/publicar": - return "Publicar Recurso" - case "/contato": - return "Entre em contato" - default: - return "Sobre" - } -} - - export default function SideBar({ setFilterState, filterState }) { let searchParams = useSearchParams(); const page = searchParams.get('page') const pathname = usePathname(); - - const getHref = (href) => { - switch (href) { - case "/sobre": - return "/sobre"; - case "/publicar": - return "/publicar"; - case "/contato": - return "/contato" - case "/perfil": - return `/perfil/${id}`; - default: - return `/busca?page=${href}`; - } - }; + const loggedIn = useLoggedIn(); + const [id, setId] = useState(null); const [needLoginOpen, setNeedLoginOpen] = useState(false); const [loggedIn, setLoggedIn] = useState(false); @@ -128,51 +101,42 @@ export default function SideBar({ setFilterState, filterState }) { window.location.reload(); } - const handleOpenLogin = () => { - if (!isLoggedIn()) { + + if (!loggedIn) { setNeedLoginOpen(true); } }; - const [id, setId] = useState(null); - useEffect(() => { - if (isLoggedIn()) { - let data = JSON.parse(getStoredValue("user_data")); - setLoggedIn(true); - setId(data["id"]); + if (loggedIn) { + let data = userData(); + setId(data?.["id"]); } - }, []); + }, [loggedIn]); return ( <> <NeedLoginModal open={needLoginOpen} setOpen={setNeedLoginOpen} /> <div className="max-md:hidden min-h-0 overflow-y-auto flex flex-col text-darkGray-HC-white-underline font-light"> - <div className="flex flex-col justify-start items-center gap-1 w-full mb-5"> + <div className="flex flex-col justify-start items-center gap-2 w-full mb-5"> {acessoRapido .filter(item => loggedIn || (item.title !== "Sair" && item.href !== "/perfil")) .map((item, index) => { return ( <Link - onClick={() => { - if (item.title === "Sair") { - handleLogout(); - } else if (item.href === "/publicar" && !loggedIn) { - handleOpenLogin(); - } - }} - href={item.href === "/publicar" ? (loggedIn ? "/publicar" : "") : getHref(item.href)} + onClick={item.href === "/publicar" ? handleOpenLogin : () => { }} + href={item.href === "/publicar" ? (loggedIn ? "/publicar" : "") : item.href} key={index} alt={item.title} title={item.title} - className={`aspect-square cursor-pointer hover:bg-lightGray-HC-white hover:text-darkGray-HC-dark-underline focus:bg-lightGray-HC-white text-center rounded-lg pt-2 w-[60%] h-[68px] + className={`aspect-square cursor-pointer hover:bg-lightGray-HC-white hover:text-darkGray-HC-dark-underline focus:bg-lightGray-HC-white text-center rounded-lg pt-2 w-[88px] ${(page === item.href) || (pathname.startsWith(item.href)) ? "bg-lightGray-HC-white text-darkGray-HC-dark-underline font-bold" : ""} `} > <item.icon - className={`rounded-lg cursor-pointer ${page === item.href ? "text-darkGray-HC-dark-underline font-bold" : ""}`} + className={`rounded-xl cursor-pointer ${page === item.href ? "text-darkGray-HC-dark-underline font-bold" : ""}`} sx={{ fontSize: '1.6em' }} /> <div className={`py-1 text-base ${page === item.href ? "text-darkGray-HC-dark-underline font-bold" : ""}`}> diff --git a/src/app/components/TypeAndFilterModal.js b/src/app/components/TypeAndFilterModal.js new file mode 100644 index 0000000000000000000000000000000000000000..10ff57c78e6cacb8ba994662c3751b507c64b793 --- /dev/null +++ b/src/app/components/TypeAndFilterModal.js @@ -0,0 +1,272 @@ +import * as React from 'react'; +import { useState, useEffect } from 'react'; +import Modal from '@mui/material/Modal'; +import CloseIcon from '@mui/icons-material/Close'; +import { Divider } from '@mui/material'; +import mecredApi from '@/axiosConfig'; +import { usePathname, useRouter } from 'next/navigation'; + + + + +//só seta a url quando finalizar o modal +function FormForFilters({ searchParams, handleClose, filtersDataAvailable, setFilters }) { + const [draftFilters, setDraftFilters] = useState({ + pesquisa: searchParams["pesquisa"] ? searchParams["pesquisa"] : "", + formato: searchParams["formato"] ? searchParams["formato"].split(',') : [], + nivel: searchParams["nivel"] ? searchParams["nivel"].split(',') : [], + idioma: searchParams["idioma"] ? searchParams["idioma"].split(',') : [], + materias: searchParams["materias"] ? searchParams["materias"].split(',') : [], + entidade: searchParams["entidade"] ? searchParams["entidade"] : "resources", + categoria: searchParams["categoria"] ? searchParams["categoria"] : "tudo" + + }); + + const handleResetFilters = (e) => { + + const filtersReset = { + formato: [], + nivel: [], + idioma: [], + materias: [], + entity: "recurso", + }; + + setDraftFilters(filtersReset); + setFilters(filtersReset); + + handleClose(); + }; + + + + //atualiza os filtros, o useEffect do content se preocupa em alterar a url + const handleSubmit = (e) => { + e.preventDefault(); + setFilters(draftFilters); + handleClose(); + }; + + const entity = [ + { name: "resources", text: "Recursos" }, + { name: "collections", text: "Coleções" }, + { name: "users", text: "Usuários" }, + ] + + + return <form className="mt-12" onSubmit={handleSubmit} onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault() }}> + <Divider /> + <div className='flex gap-4 justify-center my-8'> + {entity.map(({ name, text }) => { + return <button + type="button" + key={name} + onClick={() => setDraftFilters(prev => ({ ...prev, entidade: name }))} + className={"p-2 gap-1 rounded-md w-56 h-20 " + (draftFilters.entidade === name ? "bg-turquoise-HC-dark text-white" : "bg-lightGray-HC-dark text-darkGray-HC-white")}> + {text} + </button> + })} + </div> + + + <Divider /> + + + <p className="font-bold text-darkGray-HC-white text-xl py-2 mt-6"> + Tipo de arquivo + </p> + <div className="flex flex-wrap mt-5 mb-10 "> + {filtersDataAvailable.objectTypes.map(({ name, id }) => ( + <label key={id} className="flex items-center mr-6 mb-4 cursor-pointer"> + <input + type="checkbox" + value={name} + onChange={({ target: { value, checked } }) => + setDraftFilters(prev => ({ + ...prev, + formato: checked + ? [...(prev.formato || []), value] + : (prev.formato || []).filter(f => f !== value), + })) + } + + checked={draftFilters.formato?.includes(name)} + className="mr-2 accent-turquoise-HC-dark" + /> + <span className="text-darkGray-HC-white">{name}</span> + </label> + ))} + </div> + + <Divider /> + + + <p className="font-bold text-darkGray-HC-white text-xl py-2 mt-6"> + Nível de ensino + </p> + <div className="flex flex-wrap mt-5 mb-10 "> + {filtersDataAvailable.educationalStages.map(({ name, id }) => ( + <label key={id} className="flex items-center mr-6 mb-4 cursor-pointer"> + <input + type="checkbox" + value={name} + onChange={({ target: { value, checked } }) => + setDraftFilters(prev => ({ + ...prev, + nivel: checked + ? [...(prev.nivel || []), value] + : (prev.nivel || []).filter(f => f !== value), + })) + } + + checked={draftFilters.nivel?.includes(name)} + className="mr-2 accent-turquoise-HC-dark" + /> + <span className="text-darkGray-HC-white">{name}</span> + </label> + ))} + </div> + + + <Divider /> + + + <p className="font-bold text-darkGray-HC-white text-xl py-2 mt-6"> + Idiomas do recurso + </p> + <div className="flex flex-wrap mt-5 mb-10 "> + {filtersDataAvailable.languages.map(({ name, id }) => ( + <label key={id} className="flex items-center mr-6 mb-4 cursor-pointer"> + <input + type="checkbox" + value={name} + onChange={({ target: { value, checked } }) => + setDraftFilters(prev => ({ + ...prev, + idioma: checked + ? [...(prev.idioma || []), value] + : (prev.idioma || []).filter(f => f !== value), + })) + } + + checked={draftFilters.idioma?.includes(name)} + className="mr-2 accent-turquoise-HC-dark" + /> + <span className="text-darkGray-HC-white">{name}</span> + </label> + ))} + </div> + + <Divider /> + + + <p className="font-bold text-darkGray-HC-white text-xl py-2 mt-6"> + Grandes Áreas + </p> + <div className="flex flex-wrap mt-5 mb-10 "> + {filtersDataAvailable.subjects.map(({ name, id }) => ( + <label key={id} className="flex items-center mr-6 mb-4 cursor-pointer"> + <input + type="checkbox" + value={name} + onChange={({ target: { value, checked } }) => + setDraftFilters(prev => ({ + ...prev, + materias: checked + ? [...(prev.materias || []), value] + : (prev.materias || []).filter(f => f !== value), + })) + } + + checked={draftFilters.materias?.includes(name)} + className="mr-2 accent-turquoise-HC-dark" + /> + <span className="text-darkGray-HC-white">{name}</span> + </label> + ))} + </div> + + <div className="fixed bottom-[10%] z-20 bg-white-HC-dark p-4 w-[56%] flex justify-end"> + <div className="flex"> + <button type="button" onClick={handleResetFilters} className="text-darkGray-HC-white-underline p-2 rounded-md normal-case font-semibold text-base hover:bg-ice-HC-dark cursor-pointer mx-2 outline outline-1 outline-ice-HC-white">Remover filtros</button> + <button type="submit" + className="bg-turquoise-HC-white rounded-md p-2 hover:bg-darkTurquoise-HC-dark text-white-HC-dark-underline hover:text-white normal-case font-semibold text-base hover:bg-turquoise-hover cursor-pointer outline outline-1 outline-ice-HC-white"> + Mostrar resultados</button> + </div> + </div> + + </form> +} + + + +export default function TypeAndFilterModal({ searchParams, setFilterIsActive, open, handleClose, filters, setFilters }) { + const [filtersDataAvailable, setFiltersDataAvailable] = useState({ + objectTypes: [], + educationalStages: [], + languages: [], + subjects: [], + }); + + + useEffect(() => { + const fetchData = async () => { + try { + // faz as duas requisiçoes ao mesmo tempo + //fica mais rapido + const [objectTypesRes, educationalStageRes, languageRes, subjectsRes] = await Promise.all([ + mecredApi.get("public/objectType/all"), + mecredApi.get("public/educationalStage/all"), + mecredApi.get("public/language/all"), + mecredApi.get("public/subjects/all"), + ]); + + setFiltersDataAvailable({ + objectTypes: objectTypesRes.data, + educationalStages: educationalStageRes.data, + languages: languageRes.data, + subjects: subjectsRes.data, + }); + + } catch (error) { + console.error("Erro ao buscar dados:", error); + } + }; + fetchData(); + }, []) + + + return <div className='justify-self-end'> + <Modal + open={open} + onClose={handleClose} + aria-labelledby="modal-modal-title" + aria-describedby="modal-modal-description" + className='grid place-items-center ' + slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário + }, + }, + }} + > + <div className='flex flex-col w-[60%] h-[80%] bg-white-HC-dark overflow-x-auto rounded-lg outline outline-1 outline-ice-HC-white'> + <div> + <div className='fixed z-20 w-[60%]'> + <div className='flex justify-between bg-white-HC-dark rounded-lg p-4'> + <p className=' text-2xl font-bold text-darkGray-HC-white '> + Filtros de Pesquisa + </p> + <CloseIcon onClick={handleClose} sx={{ color: "#6c8080", fontSize: "35px" }} /> + </div> + </div> + <div className='p-6'> + <FormForFilters handleClose={handleClose} searchParams={searchParams} setFilters={setFilters} filtersDataAvailable={filtersDataAvailable} /> + </div> + </div> + </div> + </Modal> + </div> + +} diff --git a/src/app/components/UsersPageCard.js b/src/app/components/UsersPageCard.js index e868234f6cabb06e6c2e821dd0fa7f651aa55abc..5595d9853e099a65e6f19a2ffcc62e8f840e1fb4 100644 --- a/src/app/components/UsersPageCard.js +++ b/src/app/components/UsersPageCard.js @@ -15,21 +15,18 @@ import mecredApi from "@/axiosConfig"; export default function UsersPageCard({ item }) { const [achievements, setAchievements] = useState(null); - useEffect(() => { - const fetchAchievements = async (id) => { - if (!id) - return; - await mecredApi - .get(`/unlocked_achievements/user/${id}?limit=1000`) - .then(({ data }) => { - setAchievements(data); - }) - .catch(() => console.error("Não encontrou conquistas")) - } - fetchAchievements(item?.id); - }, [item]) + const fetchAchievements = async (id) => { + await mecredApi + .get(`/unlocked_achievements/user/${id}?limit=1000`) + .then(({ data }) => { + setAchievements(data); + }) + .catch(() => setError(true)) + } + fetchAchievements(item.id); + function getRandomBg(id) { const colors = [ "bg-turquoise-HC-white", @@ -40,6 +37,7 @@ export default function UsersPageCard({ item }) { "bg-pink-HC-white", "bg-red-HC-white", "bg-darkGray-HC-white", + "bg-ice-HC-white", ] return colors[id % colors.length]; @@ -49,7 +47,7 @@ export default function UsersPageCard({ item }) { return ( item && <Link tabIndex="-1" href={`/perfil/${item["id"]}`}> - <div id="conteudo" tabIndex="0" className="flex flex-col w-[280px] py-6 2xl:w-[300px] min-h-[270px] bg-ice-HC-dark m-4 rounded-lg hover:bg-ice-HC-dark-hover focus:bg-ice-HC-dark-hover outline outline-1 outline-ice-HC-white"> + <div id="conteudo" tabIndex="0" className="flex flex-col w-[300px] min-h-[270px] bg-ice-HC-dark outline outline-1 outline-lightGray-HC-white m-4 rounded-lg hover:bg-ice-HC-dark-hover focus:bg-ice-HC-dark-hover"> <div className="grid justify-items-center h-[200px] pt-2"> {item["avatar"] ? <Avatar src={mecredURL + item["avatar"]} sx={{ position: "inherit" }} className=" min-h-[120px] min-w-[120px] m-2" /> @@ -61,13 +59,13 @@ export default function UsersPageCard({ item }) { <p className=" font-bold text-darkGray-HC-white-underline text-center">{item["name"]}</p> </div> - <hr className="border-lightGray-HC-white h-1 mx-4 " /> + <hr className="border-lightGray-HC-white h-1 mx-4 "/> <div className="flex items-center flex-col gap-1 py-3 "> <div className="grid grid-cols-2 justify-start gap-1"> <div className="flex items-center mb-2"> - <img className="flex h-[20px] px-1 text-mediumGray-HC-white invertLogo-HC-white" + <img className="flex h-[20px] px-1 text-mediumGray-HC-white invertLogo-HC-white" src="/nivel.svg" - alt="Icon nivel" + alt="Icon nivel" /> <p className="mt-2 text-sm text-darkGray-HC-white-underline px-1">Nível: {item["level"]}</p> </div> @@ -84,9 +82,9 @@ export default function UsersPageCard({ item }) { <p className="text-sm text-darkGray-HC-white-underline inline px-1">Recursos: {item["learning_objects_count"]}</p> </div> <div className="flex items-center mb-2"> - <img className="flex h-[20px] px-1 invertLogo-HC-white" + <img className="flex h-[20px] px-1 invertLogo-HC-white" src="/conquistas.svg" - alt="Icon conquista" + alt="Icon conquista" /> <p className="mt-2 text-sm text-darkGray-HC-white-underline px-1">Conquistas: {achievements?.length}</p> </div> diff --git a/src/app/components/collectionPreview.js b/src/app/components/collectionPreview.js index 74d99979443cd22a0214743fdc4e2944c42cf0c5..3a7edc690c1f491480175ecde43b62aef5a07aab 100644 --- a/src/app/components/collectionPreview.js +++ b/src/app/components/collectionPreview.js @@ -50,7 +50,7 @@ const getDefaultThumbnail = (type) => { return thumbnail_url; }; -export default function CollectionPreview({ collection }) { +export default function CollectionPreview({ collection, resources }) { if (!collection?.id) return <></>; const uri = mecredURL + "inline/" + collection.id; @@ -70,7 +70,7 @@ export default function CollectionPreview({ collection }) { </div> ); - if (collection.items_thumbnails.length > 0) { + if (resources > 0) { content = ( <div className="relative aspect-video w-full h-full"> <Image @@ -81,7 +81,7 @@ export default function CollectionPreview({ collection }) { width: '100%', objectFit: 'cover' }} - src={mecredURL + collection.items_thumbnails[0]} + src={`https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${resource.id}`} alt={collection.name} /> </div> diff --git a/src/app/components/publisherInfo.js b/src/app/components/publisherInfo.js index b0433eca5caca57cc8a5d44c412ff90794ee1312..e39436f95d813120579720d1aea89cd296be8d29 100644 --- a/src/app/components/publisherInfo.js +++ b/src/app/components/publisherInfo.js @@ -1,7 +1,7 @@ import { Avatar, Button, Paper } from "@mui/material"; import { useLoginBarrier } from "@/app/handlers/loginHandler"; import { useState, useEffect } from "react"; -import mecredApi, { mecredURL } from "@/axiosConfig"; +import mecredApi, { s3URL } from "@/axiosConfig"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; export default function PublisherInfo({ publisher, disabledButton = false, userId }) { @@ -10,8 +10,7 @@ export default function PublisherInfo({ publisher, disabledButton = false, userI const loginBarrier = useLoginBarrier(); const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") + useEffect(() => { setFollowed(publisher?.followed ?? false); @@ -24,16 +23,12 @@ export default function PublisherInfo({ publisher, disabledButton = false, userI const followHandler = () => { loginBarrier(); + //passa quem eu vou seguir mecredApi.put( - `users/${publisher.id}/follow/`, - {}, + `api/user/follow/${publisher.id}`, { headers: { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, + Authorization:`bearer ${token}` }, } ); @@ -64,7 +59,7 @@ export default function PublisherInfo({ publisher, disabledButton = false, userI {publisher?.avatar ? ( <Avatar fill="true" - src={mecredURL + publisher.avatar} + src={s3URL + 'avatar/' + publisher.id} alt="Publisher Avatar" title={publisher.name} className="m-4" diff --git a/src/app/contato/page.js b/src/app/contato/page.js index bb6b42a83494968c70fc4036e947331b23e0b036..39c1939972dc0479c66ecad805c864a2b771ab0a 100644 --- a/src/app/contato/page.js +++ b/src/app/contato/page.js @@ -31,7 +31,7 @@ export default function Contact() { } await mecredApi - .post("/contacts", { + .post("public/contact", { name: formData.get("name"), email: formData.get("email"), message: formData.get("contents"), diff --git a/src/app/editar/[id]/components/UpdateInfo.js b/src/app/editar/[id]/components/UpdateInfo.js index 7467d2f4bc358ddd6961921eb2cc94de6ea5760b..cd4eda25e9bed53cdcd2cec3d041a0835210ab53 100644 --- a/src/app/editar/[id]/components/UpdateInfo.js +++ b/src/app/editar/[id]/components/UpdateInfo.js @@ -1,317 +1,177 @@ -import { Avatar, Button, Modal, TextField } from "@mui/material" +"use client" +import { Avatar, Modal, TextField } from "@mui/material" import FieldLabel from "@/app/components/FieldLabel" -import { useState, useMemo } from "react" +import { useState, useEffect } from "react" import mecredApi, { mecredURL } from "@/axiosConfig" -import { getStoredValue, saveToLocalStorage } from "@/app/handlers/localStorageHandler" import { useRouter } from "next/navigation" import ImageCropper from "@/app/components/ImageCropper" +import { authHeaders } from "@/app/handlers/loginHandler" export default function UpdateInfo({ user }) { - const [name, setName] = useState(user["name"]) - const [description, setDescription] = useState(user["description"] ? user["description"] : "") - const [sucessOpen, setSucessOpen] = useState(false) - const [notSucessOpen, setNotSucessOpen] = useState(false) - const [email, setEmail] = useState(user["email"] ?? "") - const [changePhoto, setChangePhoto] = useState(false) - - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - const router = useRouter() - const reEmail = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - - - function getRandomBg(id) { - const colors = [ - "bg-turquoise-HC-white", - "bg-orange-HC-white", - "bg-darkTurquoise-HC-white", - "bg-darkOrange-HC-white ", - "bg-violet-HC-white", - "bg-pink-HC-white", - "bg-red-HC-white", - "bg-darkGray-HC-white", - "bg-ice-HC-white", - ] - - return colors[id % colors.length]; - } - - - const handleSubmit = async (e) => { - e.preventDefault() - - if (name === "" || email === "") { - setNotSucessOpen(true) - return - } - - const payload = { - user: { - name: name, - description: description, - email: email - } - } - - const url = `/users/${user["id"]}` - - await mecredApi.put(url, payload, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + const [name, setName] = useState(user.name || "") + const [description, setDescription] = useState(user.description || "") + const [email, setEmail] = useState(user.email || "") + const [successOpen, setSuccessOpen] = useState(false) + const [notSuccessOpen, setNotSuccessOpen] = useState(false) + const [changePhoto, setChangePhoto] = useState(false) + const [imageExists, setImageExists] = useState(false) + const router = useRouter() + + const imageUrl = `${mecredURL}${user.avatar}?t=${Date.now()}` + + useEffect(() => { + const checkImage = async () => { + try { + await mecredApi.get(`public/s3/get/avatar/${user.id}`, { + headers: authHeaders() }) - .then(res => { - setSucessOpen(true); - }) - .catch(() => setNotSucessOpen(true)) - + setImageExists(true) + } catch { + setImageExists(false) + } } - - /** - * Handlers de texto - **/ - const handleNameChange = (e) => { - setName(e.target.value); + checkImage() + }, [user.id]) + + const handleSubmit = async (e) => { + e.preventDefault() + if (!name || !email) { + setNotSuccessOpen(true) + return } - const handleDescriptionChange = (e) => { - setDescription(e.target.value); - } - - const handleEmailChange = (e) => { - setEmail(e.target.value); - } - - /* - * Modals - **/ - const ModalSucess = ({ open, onClose }) => { - return ( - <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário - }, - }, - }}> - <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> - <p className="text-xl justify-center flex text-darkGray-HC-white-underline mb-2">Perfil editado com sucesso!</p> - <div className="flex flex-row mt-2"> - - <button - className=" text-sm p-2 mr-1 text-white-HC-dark-underline border-main rounded-lg normal-case h-9 font-bold bg-turquoise hover:bg-turquoise-hover" - onClick={() => router.push(`/perfil/${user["id"]}`)} - > - Voltar para perfil - </button> - <button - className=" text-sm p-2 ml-1 text-darkGray-HC-white-underline border-main rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-ice-HC-dark-hover" - onClick={() => onClose()} - > - Continuar editando - </button> - </div> - </div> - </Modal> - ) + try { + await mecredApi.post(`api/user/update`, { + id: user.id, + name, + email, + description + }, { + headers: authHeaders() + }) + setSuccessOpen(true) + } catch { + setNotSuccessOpen(true) } + } + + const ModalNotSuccess = ({ open, onClose }) => ( + <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ + backdrop: { sx: { backgroundColor: "rgba(0, 0, 0, 0.3)" } } + }}> + <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> + <p className="text-xl text-darkGray-HC-white-underline mb-2 text-center">Não foi possível fazer as alterações</p> + <div className="flex justify-center mt-2"> + <button onClick={() => router.push(`/perfil/${user.id}`)} className="bg-turquoise text-white p-2 mr-2 rounded-lg"> + Voltar para perfil + </button> + <button onClick={onClose} className="bg-ice-HC-dark text-darkGray-HC-white p-2 ml-2 rounded-lg"> + Continuar editando + </button> + </div> + </div> + </Modal> + ) + + const ModalSuccess = ({ open, onClose }) => ( + <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ + backdrop: { sx: { backgroundColor: "rgba(0, 0, 0, 0.3)" } } + }}> + <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> + <p className="text-xl text-green-600 mb-2 text-center">Alterações salvas com sucesso!</p> + <div className="flex justify-center mt-2"> + <button onClick={() => router.push(`/perfil/${user.id}`)} className="bg-green-500 text-white p-2 rounded-lg"> + Ir para perfil + </button> + </div> + </div> + </Modal> + ) + + const ModalImageCropper = ({ open, onClose }) => ( + <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ + backdrop: { sx: { backgroundColor: "rgba(0, 0, 0, 0.3)" } } + }}> + <div className="flex flex-col bg-ice-HC-dark p-6 rounded-lg overflow-auto" style={{ width: '90%', maxWidth: '600px', height: '70vh', maxHeight: '90vh' }}> + <p className="text-xl text-darkGray-HC-white text-center mb-4">Editar Foto de Perfil</p> + <div className="flex-grow"> + <ImageCropper userId={user.id} setChangePhoto={setChangePhoto} /> + </div> + <button onClick={onClose} className="bg-ice-HC-dark text-darkGray-HC-white p-2 mt-4 rounded-lg"> + Cancelar + </button> + </div> + </Modal> + ) + + return ( + <div> + <ModalNotSuccess open={notSuccessOpen} onClose={() => setNotSuccessOpen(false)} /> + <ModalSuccess open={successOpen} onClose={() => setSuccessOpen(false)} /> + <ModalImageCropper open={changePhoto} onClose={() => setChangePhoto(false)} /> + + <form onSubmit={handleSubmit}> + <h2 className="text-3xl text-darkGray-HC-white font-bold mt-3 text-center">Editar Perfil</h2> + + <div className="flex flex-col mt-4 items-center"> + <label className="text-xl text-darkGray-HC-white font-bold">Foto de perfil</label> + <div className="flex flex-col items-center my-2"> + {imageExists && user.avatar ? ( + <Avatar src={imageUrl} alt="Foto de perfil" className="h-[276px] w-[276px]" /> + ) : ( + <div className="flex items-center justify-center text-8xl font-bold text-ice-HC-dark bg-turquoise-HC-white rounded-full h-[276px] w-[276px]"> + {user.name[0]} + </div> + )} + <button type="button" onClick={() => setChangePhoto(true)} className="bg-turquoise text-white mt-2 px-4 py-2 rounded-lg"> + Editar Foto + </button> + </div> + </div> - const ModalNotSucess = ({ open, onClose }) => { - return ( - <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário - }, - }, - }}> - <div className="flex flex-col justify-center bg-ice-HC-dark p-5 rounded"> - <p className="text-xl justify-center flex text-darkGray-HC-white-underline mb-2">Não foi possível fazer as alterações</p> - <div className="flex flex-row mt-2"> - - <button - className=" text-sm p-2 mr-1 text-white-HC-dark-underline border-main rounded-lg normal-case h-9 font-bold bg-turquoise hover:bg-turquoise-hover" - onClick={() => router.push(`/perfil/${user["id"]}`)} - > - Voltar para perfil - </button> - <button - className=" text-sm p-2 ml-1 text-darkGray-HC-white-underline border-main rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-ice-HC-dark-hover" - onClick={() => onClose()} - > - Continuar editando - </button> - </div> - </div> - </Modal> - ) - } - - const ModalImageCropper = ({ open, onClose }) => { - return ( - <Modal - open={open} - onClose={onClose} - className="grid place-items-center" - slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário - }, - }, - }} - > - <div - className="flex flex-col bg-ice-HC-dark p-6 rounded-lg outline outline-1 outline-ice-HC-white overflow-auto" - style={{ - width: '90%', - maxWidth: '600px', - height: '70vh', - maxHeight: '90vh', - }} - > - <p className="text-xl text-darkGray-HC-white-underline text-center mb-4">Editar Foto de Perfil</p> - - <div className="flex-grow"> - <ImageCropper payloadHeader={"user[avatar]"} type={"users"} userId={user.id} setChangePhoto={setChangePhoto} /> - </div> - - <button - className="text-sm p-2 text-darkGray-HC-white-underline border-main rounded-lg h-9 font-bold bg-ice-HC-dark hover:bg-ice-HC-dark-hover mt-4" - onClick={onClose} - > - Cancelar - </button> - </div> - </Modal> - ) - } - - - return ( - <div> - <ModalNotSucess open={notSucessOpen} onClose={() => { setNotSucessOpen(false) }} /> - <ModalSucess open={sucessOpen} onClose={() => { setSucessOpen(false) }} /> - <form onSubmit={handleSubmit}> - <label className="text-3xl text-darkGray-HC-white font-bold mt-3 flex justify-center">Editar Perfil</label> - <div className="flex flex-col mt-4 items-center"> - <label className="text-xl text-darkGray-HC-white font-bold mx-6">Foto de perfil</label> - <div className="flex flex-col items-center my-2"> - {user["avatar"] ? ( - <Avatar - src={mecredURL + user["avatar"] + `?t=${new Date().getTime()}`} - sx={{ position: "inherit" }} - alt="Foto de perfil" - className=" h-[276px] w-[276px] " - /> - ) : ( - <div - className={`flex items-center justify-center text-xl font-bold text-ice-HC-dark rounded-full h-[150px] w-[150px] ${getRandomBg(user["id"])}`} - > - {user["name"][0]} - </div> - )} - - <button - type="button" - className="bg-turquoise-HC-white hover:bg-darkTurquoise-HC-dark text-white-HC-dark-underline hover:text-white rounded-lg mt-2 px-4 py-2 outline outline-1 outline-ice-HC-white" - onClick={() => setChangePhoto(true)} - > - Editar Foto - </button> - - <ModalImageCropper open={changePhoto} onClose={() => setChangePhoto(false)} /> - </div> - </div> + <div className="mx-4 mt-4"> + <label className="text-xl text-darkGray-HC-white font-bold">Nome Completo *</label> + <TextField + value={name} + onChange={(e) => setName(e.target.value)} + fullWidth + className="mt-2" + /> + </div> - <div className="mx-4 mt-4"> - <label className=" text-xl text-darkGray-HC-white font-bold ">Nome Completo *</label> - <TextField - onChange={handleNameChange} - className="w-[100%] mt-2 outline outline-1 outline-ice-HC-white text-ice-HC-white" - defaultValue={user["name"]} - sx={{ - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'var(--mediumGray-HC-white)', // cor da borda - }, - '&:hover fieldset': { - borderColor: 'var(--turquoise-HC-white)', // ao passar o mouse - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--turquoise-HC-white)', // quando estiver focado - }, - "& input": { - color: "var(--darkGray-HC-white)", - }, - }, - }} - /> - </div> - <div className="mx-4"> - <FieldLabel name="Sobre mim" description="Visível no seu perfil público" /> - <TextField - multiline - rows={3} - onChange={handleDescriptionChange} - helperText={`${description.length}/160`} - error={description.length > 160} - className="w-[100%] outline outline-1 outline-ice-HC-white" - defaultValue={user["description"]} - sx={{ - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'var(--mediumGray-HC-white)', // cor da borda - }, - '&:hover fieldset': { - borderColor: 'var(--turquoise-HC-white)', // ao passar o mouse - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--turquoise-HC-white)', // quando estiver focado - }, - "& input": { - color: "var(--darkGray-HC-white)", - }, - }, - }} - /> - </div> - <div className="mx-4 mt-8"> - <label className=" text-xl text-darkGray-HC-white font-bold">Email *</label> - <TextField - onChange={handleEmailChange} - className="w-[100%] mt-2 outline outline-1 outline-ice-HC-white" - defaultValue={user["email"]} - sx={{ - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'var(--mediumGray-HC-white)', // cor da borda - }, - '&:hover fieldset': { - borderColor: 'var(--turquoise-HC-white)', // ao passar o mouse - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--turquoise-HC-white)', // quando estiver focado - }, - "& input": { - color: "var(--darkGray-HC-white)", - }, - }, - }} - /> - </div> + <div className="mx-4 mt-4"> + <FieldLabel name="Sobre mim" description="Visível no seu perfil público" /> + <TextField + multiline + rows={3} + value={description} + onChange={(e) => setDescription(e.target.value)} + helperText={`${description.length}/160`} + error={description.length > 160} + fullWidth + className="mt-2" + /> + </div> - <div className="flex justify-end mt-5 mr-4"> + <div className="mx-4 mt-4"> + <label className="text-xl text-darkGray-HC-white font-bold">Email *</label> + <TextField + value={email} + onChange={(e) => setEmail(e.target.value)} + fullWidth + className="mt-2" + /> + </div> - <button type="button" onClick={() => router.push(`/perfil/${user["id"]}`)} className="bg-white-HC-dark hover:bg-white text-darkGray-HC-white-underline hover:text-darkGray-HC-dark p-2 rounded-lg mr-1 outline outline-1 outline-ice-HC-white ">Voltar para perfil</button> - <button type="submit" className="bg-turquoise-HC-white hover:bg-darkTurquoise-HC-dark text-white-HC-dark-underline hover:text-white p-2 rounded-lg ml-1 outline outline-1 outline-ice-HC-white">Salvar alterações</button> - </div> - </form> + <div className="flex justify-end mt-5 mx-4"> + <button type="button" onClick={() => router.push(`/perfil/${user.id}`)} className="bg-white text-darkGray-HC-white p-2 rounded-lg mr-2"> + Voltar para perfil + </button> + <button type="submit" className="bg-turquoise text-white p-2 rounded-lg"> + Salvar alterações + </button> </div> - ) + </form> + </div> + ) } diff --git a/src/app/editar/[id]/components/UpdatePassword.js b/src/app/editar/[id]/components/UpdatePassword.js index 65f4741381934cc036f12ec8e1f797782fcd1bba..1824775dec7bb4153425f81945503874af731173 100644 --- a/src/app/editar/[id]/components/UpdatePassword.js +++ b/src/app/editar/[id]/components/UpdatePassword.js @@ -3,6 +3,7 @@ import { Modal, TextField } from "@mui/material" import { useState } from "react" import { getStoredValue } from "@/app/handlers/localStorageHandler" import { useRouter } from "next/navigation" +import { authHeaders } from "@/app/handlers/loginHandler" export default function UpdatePassword({ user }) { const [currentPassword, setCurrentPassword] = useState("") @@ -11,21 +12,6 @@ export default function UpdatePassword({ user }) { const [equal, setEqual] = useState(true) const [notSucessOpen, setNotSucessOpen] = useState(false) const [sucessOpen, setSucessOpen] = useState(false) - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - const authHeaders = { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - } - const router = useRouter() - - const handleCurr = (e) => { setCurrentPassword(e.target.value) @@ -59,14 +45,15 @@ export default function UpdatePassword({ user }) { return } - await mecredApi.put(url, payload, authHeaders) + await mecredApi.put(url, payload, { + headers: authHeaders() + }) .then(() => setSucessOpen(true)) .catch(() => setNotSucessOpen(true)) - - } + const ModalNotSucess = ({ open, onClose }) => { return ( <Modal open={open} onClose={onClose} className="grid place-items-center" slotProps={{ diff --git a/src/app/editar/[id]/page.js b/src/app/editar/[id]/page.js index a9a984d529cf7d2b2f60bf56cc981e798bf709da..3fc8c66946a04631043d2ae86aa00329d5ac5bab 100644 --- a/src/app/editar/[id]/page.js +++ b/src/app/editar/[id]/page.js @@ -4,30 +4,21 @@ import EditForm from "./components/EditForm"; import { useEffect, useState } from "react"; import mecredApi from "@/axiosConfig"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; -import { useLoginBarrier } from "@/app/handlers/loginHandler"; +import { authHeaders, useLoginBarrier } from "@/app/handlers/loginHandler"; import Loading from "../../components/Loading"; export default function Edit({ params }) { const [userData, setUserData] = useState() - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") const loginBarrier = useLoginBarrier() const [got, setGot] = useState(false) useEffect(() => { - if (!loginBarrier()) - return + // if (!loginBarrier()) + // return const fetchUser = async (id) => { await mecredApi - .get(`/users/${id}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + .get(`api/user/${id}`, { + headers: authHeaders() }) .then(({data}) => { setUserData(data); @@ -37,7 +28,7 @@ export default function Edit({ params }) { } fetchUser(params.id) - }, [params.id, token, client, uid, loginBarrier]) + }, [params.id]) const ErrorEdit = () => { return ( diff --git a/src/app/entrar/components/LoginForm.js b/src/app/entrar/components/LoginForm.js index 3b1bf807d81ce507eae6bb4bc2a6e2afe5d46f45..b8679fdfccf6b5dcf4033a3acce5b28c9ca0a970 100644 --- a/src/app/entrar/components/LoginForm.js +++ b/src/app/entrar/components/LoginForm.js @@ -18,6 +18,7 @@ import SignupModal from "./SignupModal"; import PasswordModal from "./PasswordModal"; import Title from "@/app/components/Title"; import { useRouter } from "next/navigation"; +import { GovBRSignInButton } from "@/app/components/GovBrSignInButton"; export default function LoginForm({ @@ -25,6 +26,7 @@ export default function LoginForm({ handlePasswordChange, handleSubmit, errorMessage, + suggestionMessage, uppercase, }) { const [openModalSignup, setOpenModalSignup] = useState(false); @@ -47,7 +49,7 @@ export default function LoginForm({ <div className="xl:hidden"> <Title /> </div> - <div className="flex flex-col "> + <div className="flex flex-col gap-3"> <form onSubmit={handleSubmit} className="mb-5"> <Paper elevation={0} className="flex flex-col bg-white-HC-dark w-96 items-center p-5 rounded-lg outline outline-1 outline-ice-HC-white"> <Image @@ -60,6 +62,9 @@ export default function LoginForm({ {errorMessage ? ( <Alert severity="error" className="mb-3">{errorMessage}</Alert> ) : null} + {suggestionMessage ? ( + <Alert severity="info" className="mb-3">{suggestionMessage}</Alert> + ) : null} {/* Campo de E-mail */} <div className="w-full mb-6"> @@ -136,16 +141,7 @@ export default function LoginForm({ <PasswordModal open={openModalPasswd} handleClose={handleCloseModalPasswd} /> </Paper> </form> - {/* <Button - title="Ainda não disponível" - fullWidth - disableElevation - variant="outlined" - className="my-2 bg-white-HC-dark border-white text-[#B6CCCC] normal-case flex gap-2 w-96 font-bold" - > - <span >Entrar com o</span> - <Image className="w-14 h-6" src="/govbr.svg" alt="govbr" width={10} height={10} /> - </Button> */} + <GovBRSignInButton /> <button onClick={() => router.push("/sobre")} className="text-turquoise-HC-white-underline outline outline-1 outline-ice-HC-white lg:text-nowrap bg-white-HC-dark py-1 w-96 hover:bg-ice-HC-dark-hover rounded cursor-pointer" diff --git a/src/app/entrar/page.js b/src/app/entrar/page.js index 5971e90194ecc8a768aa733c5a49448285508370..b0858217e9b8a440303fe7ecbc8bb8048627582f 100644 --- a/src/app/entrar/page.js +++ b/src/app/entrar/page.js @@ -13,63 +13,65 @@ import { ThemeProvider } from "@emotion/react"; import theme from "../theme"; import LoginInfo from "./components/LoginInfo"; import AcessibilityBar from "../components/AcessibilityBar"; +import { logIn, updateAccessToken } from "../handlers/loginHandler"; +import Loading from "../components/Loading"; function Login() { const [userEmail, setUserEmail] = useState(""); const [userPassword, setUserPassword] = useState(""); const [errorMessage, setErrorMessage] = useState(""); + const [suggestionMessage, setSuggestionMessage] = useState(""); const [isFirstLetterUpperCase, setIsFirstLetterUpperCase] = useState(false); const searchParams = useSearchParams(); - const access_token = getStoredValue("access_token"); + const router = useRouter(); + + useEffect(() => { + if (searchParams.has("token")) { + updateAccessToken(searchParams.get("token")) + .then(() => { + localStorage.setItem('provider', 'govbr'); + router.push("/sobre"); + }); + } + }, [searchParams, router]); + + if (searchParams.has("token")) { + // se temos um token não renderizar página normal. + return <Loading />; + } + // Obtendo os parâmetros de busca const redirectUrl = searchParams.get("redirect") ? searchParams.get("redirect") + (searchParams.get("params") ? `?${searchParams.get("params")}` : '') : "/sobre"; // Redireciona para /sobre se não houver valor - const router = useRouter(); const handleEmailChange = (e) => { const emailValue = e.target.value; setUserEmail(emailValue); // Verifica se a primeira letra é maiúscula - if (emailValue.length > 0) { - const firstLetter = emailValue.charAt(0); - setIsFirstLetterUpperCase(firstLetter === firstLetter.toUpperCase()); - } else { - setIsFirstLetterUpperCase(false); - } + if (emailValue.length > 0) { + const firstLetter = emailValue.charAt(0); + setIsFirstLetterUpperCase(firstLetter === firstLetter.toUpperCase()); + } else { + setIsFirstLetterUpperCase(false); + } }; - const handlePasswordChange = (e) => { setUserPassword(e.target.value); }; - const userLogin = async (event, email, password) => { + const userLogin = async (event) => { event.preventDefault(); - await mecredApi - .post("/auth/sign_in", { - email: userEmail, - password: userPassword, - }) - .then((response) => { - saveToLocalStorage("user_data", JSON.stringify(response.data["data"])); - saveToLocalStorage("access_token", response.headers["access-token"]); - saveToLocalStorage("client", response.headers["client"]) - saveToLocalStorage("uid", response.headers["uid"]) - saveToLocalStorage("expiry", response.headers["expiry"]) - saveToLocalStorage("token-type", response.headers["token-type"]) - router.push(redirectUrl); - }) + await logIn(userEmail, userPassword) .catch((error) => { - setErrorMessage(error['response']['data']['errors'][0]) + console.log(error); + setErrorMessage(error['response']['data']['error']['message']); + setSuggestionMessage(error['response']['data']['error']['suggestion']); }) }; - useEffect(() => { - if (access_token) redirect(redirectUrl); - }); - return ( <ThemeProvider theme={theme}> <Grid container className="bg-fundo bg-repeat bg-fixed min-h-screen"> @@ -82,6 +84,7 @@ function Login() { handlePasswordChange={handlePasswordChange} handleSubmit={userLogin} errorMessage={errorMessage} + suggestionMessage={suggestionMessage} uppercase={isFirstLetterUpperCase} /> </Grid> @@ -99,4 +102,4 @@ export default function LoginWrapper() { </div> </Suspense> ); -}; \ No newline at end of file +}; diff --git a/src/app/globals.css b/src/app/globals.css index ef4ead286723e464e56f0860687ac6678ac5b59a..8a44d2fac5161fc8a9ae03299f00c924345d5886 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -75,6 +75,10 @@ --lightGray-HC-white: #d8e6e6; --lightGray-HC-white-underline:#d8e6e6; --lightGray-HC-dark-underline:#d8e6e6; + --govBrBlue-HC-white: #1351b4; + --govBrBG-HC-dark: #ffffff; + --govBrHover-HC-dark: #d3dded; + --govBrActive-HC-white: #91add9; --red-HC-white: #dc2626; --mediumGray-HC-white: #b9cccc; --mediumGray-HC-dark: #b9cccc; @@ -87,7 +91,7 @@ --logo-square: url("/mecred_square.svg"); --input-focus: #00bacc; --filter: none; - + --filter-dontChange-HC-white: brightness(0) invert(1); --font-size-xs-default: 12px; --line-height-xs-default: 16px; diff --git a/src/app/handlers/loginHandler.js b/src/app/handlers/loginHandler.js index 59dd6c7edb3b146f5deeea5c258a8a721290e32b..1bff21899cb468be57939c4dfcb9704273784aca 100644 --- a/src/app/handlers/loginHandler.js +++ b/src/app/handlers/loginHandler.js @@ -1,13 +1,7 @@ -import { redirect, usePathname, useSearchParams } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; -import { useCallback } from "react"; -import { getStoredValue } from "./localStorageHandler"; - -export function isLoggedIn() { - if (typeof window === "undefined") return undefined; - - return getStoredValue("access_token") ? true : false; -} +import { useCallback, useEffect, useState } from "react"; +import mecredApi, { govBrLogOutURL } from "@/axiosConfig"; /** * React Hook for forcing login. Redirects to login page and when user logs in redirects back to the original page. @@ -16,13 +10,13 @@ export function isLoggedIn() { * * @returns Function that will force login. */ -export function useLoginBarrier(originalPath) { +export function useLoginBarrier() { const pathname = usePathname(); const searchParams = useSearchParams(); const router = useRouter(); - + return useCallback(() => { - if (isLoggedIn()) return true; + if (localStorage.getItem('token')) return true; const params = new URLSearchParams(); params.set("redirect", pathname); @@ -31,4 +25,77 @@ export function useLoginBarrier(originalPath) { return false; }, [router, pathname, searchParams]); +} + +export function authHeaders() { + const token = localStorage.getItem('token'); + return { + 'Authorization': `Bearer ${token}` + }; +} + +export function useLoggedIn() { + const [loggedIn, setLoggedIn] = useState(false); + + useEffect(() => { + setLoggedIn(isLoggedIn()); + }, [setLoggedIn]); + + return loggedIn; +} + +export function isLoggedIn() { + return localStorage.getItem('token') ? true : false; +} + +export function logIn(email, password) { + return mecredApi + .post("/public/auth/signin", { + email: email, + password: password, + }) + .then((response) => { + const token = response.data['token']; + localStorage.setItem('provider', 'password'); + updateAccessToken(token); + }); +} + +export async function updateAccessToken(token) { + localStorage.setItem('token', token); + await fetchUser(); +} + +export function logOut() { + localStorage.removeItem('token'); + localStorage.removeItem('user_data'); + + const provider = localStorage.getItem('provider'); + + if (provider === "govbr") { + //const form = document.createElement("form"); + //form.setAttribute("method", "post"); + //form.setAttribute("action", govBrLogOutURL); + //document.body.appendChild(form); + //form.submit(); + localStorage.removeItem('provider'); + window.location.replace(govBrLogOutURL); + } +} + +export function userData() { + const dataString = localStorage.getItem('user_data'); + if (!dataString) + return undefined; + + const data = JSON.parse(dataString); + return data; +} + +export async function fetchUser() { + const response = await mecredApi.get('/api/user/me', + { headers: authHeaders() } + ); + const dataString = JSON.stringify(response.data); + localStorage.setItem('user_data', dataString); } \ No newline at end of file diff --git a/src/app/perfil/[id]/components/CardsComplaints.js b/src/app/perfil/[id]/components/CardsComplaints.js index 21da89c28b884c295d2aa4f5df0dd5a61b0b71c9..9d771190c3f6fbf0f45ebb0bb2bc2fa2e71bbeae 100644 --- a/src/app/perfil/[id]/components/CardsComplaints.js +++ b/src/app/perfil/[id]/components/CardsComplaints.js @@ -32,14 +32,14 @@ export default function CardsComplaints({ item, key}) { }} component="img" image="/images/preview-recurso-denunciado.png" - title={item[0]?.name} + title={item?.name} alt="img" /> <CardHeader className="self-start flex-shrink h-[100px]" title={ <Typography variant="body2" color="" className="line-clamp-2 text-darkGray-HC-white-underline font-bold"> - {item[0]?.name} + {item?.name} </Typography> } subheader={ @@ -49,7 +49,7 @@ export default function CardsComplaints({ item, key}) { color="text.secondary" className="line-clamp-1 text-darkGray-HC-white font-light" > - {item[0]?.name} + {item?.name} </Typography> </> } @@ -57,7 +57,7 @@ export default function CardsComplaints({ item, key}) { <div className="hover:bg-ice-HC-dark-hover flex items-end rounded-lg"> <Button className="text-darkGray-HC-white-underline font-light" alt='Avaliar' - onClick={() => window.open(location.protocol + `//` + location.host + `/recurso/${item[0]?.learning_object_id}`)} + onClick={() => window.open(location.protocol + `//` + location.host + `/recurso/${item?.id}`)} > Avaliar </Button> diff --git a/src/app/perfil/[id]/components/CardsHomologation.js b/src/app/perfil/[id]/components/CardsHomologation.js index 7459d760876b831ef41a86c632eb97806f217e8f..2ca6c16555e95a9f581fd676433d8c56608efa65 100644 --- a/src/app/perfil/[id]/components/CardsHomologation.js +++ b/src/app/perfil/[id]/components/CardsHomologation.js @@ -5,6 +5,7 @@ import Avatar from "@mui/material/Avatar"; import Typography from "@mui/material/Typography"; import Link from "next/link"; import { mecredURL } from "@/axiosConfig"; +import { useEffect, useState } from "react"; const getDefaultThumbnail = (type) => { let thumbnail_url; @@ -51,7 +52,35 @@ const getDefaultThumbnail = (type) => { * @returns Cards de recursos para homologação */ export default function CardsHomologation({ item, key, tag }) { + + const [imageMap, setImageMap] = useState({}); + + const verifyImage = async (id) => { + try { + await mecredApi.get(`public/s3/get/thumbnail/resource/${id}`, { + headers: authHeaders() + }); + return true; // Existe + } catch (error) { + if (error.response && error.response.status === 404) { + return false; // Não existe + } + return false; + } + }; + + useEffect(() => { + const checkImage = async () => { + const exists = await verifyImage(item.id); + setImageMap({ [item.id]: exists }); + }; + if (item) { + checkImage(); + } + }, [item]); + + return ( <Card className="transition bg-white-HC-dark outline outline-1 outline-ice-HC-white items-center mt-5 mx-2 min-h-[320px]" @@ -78,22 +107,21 @@ export default function CardsHomologation({ item, key, tag }) { }} component="img" image={ - tag ? null : - item.learning_object?.thumbnail === null - ? getDefaultThumbnail(item.learning_object?.object_type) - : mecredURL + item.learning_object?.thumbnail - } - title={ tag ? item[0]?.name : item.learning_object?.thumbnail} + imageMap[item.id] + ? `https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${item.id}` + : getDefaultThumbnail(item.objectTypeName) + } + title={item[0]?.name} alt="img" /> <CardHeader className="self-start flex-shrink h-[100px]" - avatar={tag ? null : item.submitter?.avatar === null ? null : + avatar={ <Link href="/perfil"> <Avatar - src={mecredURL + item.submitter?.avatar} - alt={item.submitter?.name} - title={item.submitter?.name} + src={`https://s3.c3sl.ufpr.br/mecredteste/avatar/${item.user_id}`} + alt={item.author} + title={item.author} // className="-mt-9" sx={{ width: 28, @@ -106,7 +134,7 @@ export default function CardsHomologation({ item, key, tag }) { } title={ <Typography variant="body2" color="" className="line-clamp-2 text-darkGray-HC-white-underline font-bold"> - { tag ? item[0]?.name : item.learning_object?.name} + { tag ? item[0]?.name : item.name} </Typography> } subheader={ @@ -116,7 +144,7 @@ export default function CardsHomologation({ item, key, tag }) { color="text.secondary" className="line-clamp-1 text-darkGray-HC-white-underline font-light" > - {tag ? item[0]?.name : item.submitter?.name} + {tag ? item[0]?.name : item.author} </Typography> </> } diff --git a/src/app/perfil/[id]/components/CreateCollectionModal.js b/src/app/perfil/[id]/components/CreateCollectionModal.js index c21c4d3d84edc96a909b4aaab5b51394beae6a83..b7687457181eee8b3f3ab3ddfdf383b7aec5bd34 100644 --- a/src/app/perfil/[id]/components/CreateCollectionModal.js +++ b/src/app/perfil/[id]/components/CreateCollectionModal.js @@ -2,6 +2,7 @@ import { FormLabel, Modal, TextField, RadioGroup, FormControlLabel, Radio } from import { useState } from "react"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import mecredApi from "@/axiosConfig"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @param {Object} props @@ -61,26 +62,19 @@ export default function CreateCollectionModal({ open, onClose, idLogin }) { * Informações necessárias para o backend para criar uma nova coleção */ const payload = { - "collection": { - "name": name, - "owner_id": idLogin, - "owner_type": "User", - "privacy": e.target["privacy-radio-group"].value - } + "user_id": idLogin, + "name": name, + "privacy": e.target["privacy-radio-group"].value }; /** * Requisição para criação da coleção */ + console.log(typeof idLogin); // deve mostrar "number" se estiver certinho + try { // Requisição para criação da coleção - const response = await mecredApi.post("/collections", payload, { - headers: { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }, + const response = await mecredApi.post("api/collections/create", payload, { + headers: authHeaders(), }); setOpenSuccess(true); setName(""); @@ -159,30 +153,16 @@ export default function CreateCollectionModal({ open, onClose, idLogin }) { <FormLabel > <RadioGroup name="privacy-radio-group"> <FormControlLabel - value="public" - control={<Radio - sx={{ - color: 'var(--darkGray-HC-white)', - '&.Mui-checked': { - color: 'var(--turquoise-HC-white)', - }, - }} - />} + value="false" + control={<Radio className="" />} label="Coleção Pública" sx={{ color: 'var(--darkGray-HC-white)', // muda a cor da fonte do rótulo }} /> <FormControlLabel - value="private" - control={<Radio - sx={{ - color: 'var(--darkGray-HC-white)', - '&.Mui-checked': { - color: 'var(--turquoise-HC-white)', - }, - }} - />} + value="true" + control={<Radio />} label="Coleção Privada" sx={{ color: 'var(--darkGray-HC-white)', // muda a cor da fonte do rótulo diff --git a/src/app/perfil/[id]/components/EditCollectionModal.js b/src/app/perfil/[id]/components/EditCollectionModal.js index 0dbd8af3027b3da481bb0fbbb3081ac7ce80eccd..74cc0df4b12092e668c34bfb90fe1bf923c4363b 100644 --- a/src/app/perfil/[id]/components/EditCollectionModal.js +++ b/src/app/perfil/[id]/components/EditCollectionModal.js @@ -3,6 +3,7 @@ import { Modal, TextField, RadioGroup, FormControlLabel, Radio, FormLabel } from import { getStoredValue } from "@/app/handlers/localStorageHandler"; import Checkbox from '@mui/material/Checkbox'; import mecredApi from "@/axiosConfig"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @param {Object} props @@ -54,31 +55,16 @@ export default function EditCollectionModal({ open, onClose, collectionId, colle const handleSubmit = async (e) => { e.preventDefault(); - - if (name.length > 150) { - alert("O nome não pode ultrapassar 150 caracteres."); - return; - } - - const payload = { - collection: { - name, - owner_id: collectionData.owner_id, - owner_type: collectionData.owner_type, - privacy, - }, + id: collectionId, + name, + owner_id: collectionData.owner_id, + owner_type: collectionData.owner_type, + privacy, }; - try { - const response = await mecredApi.put(`/collections/${collectionId}`, payload, { - headers: { - "access-token": token, - "token-type": "Bearer", - client, - uid, - Expires: 0, - }, + const response = await mecredApi.post(`api/collections/update`, payload, { + headers: authHeaders(), }); await deleteResourceCollection(); @@ -121,141 +107,91 @@ export default function EditCollectionModal({ open, onClose, collectionId, colle return ( - <Modal open={open} - onClose={() => { - setSelectedResources([]); - onClose(); - }} - className="grid place-items-center m-5" slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário + <Modal open={open} onClose={onClose} className="grid place-items-center m-5" slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", // Ajuste a opacidade conforme necessário + }, + }, + }}> + <div className="flex flex-col bg-ice-HC-dark p-5 rounded-lg w-[40%] max-sm:w-[85%] outline outline-1 outline-ice-HC-white"> + <div className="text-2xl font-bold text-darkGray-HC-white">Editar Coleção</div> + <p className="text-lg text-darkGray-HC-white font-bold mb-1 mt-4">Nome da Coleção*</p> + <form onSubmit={handleSubmit}> + <TextField + size="small" + fullWidth + onChange={(e) => setName(e.target.value)} + value={name} + required + sx={{ + '& .MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'var(--mediumGray-HC-white)', // cor da borda + }, + '&:hover fieldset': { + borderColor: 'var(--turquoise-HC-white)', // ao passar o mouse + }, + '&.Mui-focused fieldset': { + borderColor: 'var(--turquoise-HC-white)', // quando estiver focado + }, + '& input': { + color: 'var(--darkGray-HC-white)', // cor do texto digitado }, }, }} - > - <div className="flex flex-col bg-ice-HC-dark p-5 rounded-lg w-[40%] max-sm:w-[85%] outline outline-1 outline-ice-HC-white max-h-[50vh]"> - <div className="text-2xl font-bold text-darkGray-HC-white">Editar Coleção</div> - <form onSubmit={(e) => { - e.preventDefault(); - handleSubmit(); - }} - className="flex flex-col flex-grow overflow-y-auto max-h-full" - > - <div className="overflow-y-auto max-h-[50vh] mr-2 pr-2"> - <p className="text-lg text-darkGray-HC-white font-bold mb-1 mt-4">Nome da Coleção*</p> - <TextField - size="small" - fullWidth - onChange={(e) => setName(e.target.value)} - value={name} - required - error={name.length > 150} - helperText={ - name.length > 150 - ? `O nome excede o limite de 150 caracteres (${name.length}/150)` - : `${name.length}/150` - } + /> + <p className="text-lg text-darkGray-HC-white font-bold mb-3 mt-4">Esta coleção é:</p> + <FormLabel> + <RadioGroup + value={privacy} + sx={{ + '& .MuiRadio-root': { + color: 'var(--darkGray-HC-white)', // Default radio color + '&.Mui-checked': { + color: 'var(--turquoise-HC-white)', // Selected radio color + }, + }, + }} + onChange={(e) => setPrivacy(e.target.value)}> + <FormControlLabel + value="public" + control={<Radio />} + label="Coleção Pública" sx={{ - '& .MuiOutlinedInput-root': { - '& fieldset': { - borderColor: 'var(--mediumGray-HC-white)', - }, - '&:hover fieldset': { - borderColor: 'var(--turquoise-HC-white)', - }, - '&.Mui-focused fieldset': { - borderColor: 'var(--turquoise-HC-white)', - }, - '& input': { - color: 'var(--darkGray-HC-white)', - }, + '& .MuiFormControlLabel-label': { + color: 'var(--darkGray-HC-white)', }, - '& .MuiFormHelperText-root': { - color: name.length > 150 ? 'var(--red-HC-white)' : 'var(--darkGray-HC-white)', // vermelho ou padrão - fontWeight: 'bold', + }} + /> + <FormControlLabel + value="private" + control={<Radio />} + label="Coleção Privada" + sx={{ + '& .MuiFormControlLabel-label': { + color: 'var(--darkGray-HC-white)', }, }} /> - <p className="text-lg text-darkGray-HC-white font-bold mb-3 mt-4">Esta coleção é:</p> - <FormLabel> - <RadioGroup - value={privacy} - onChange={(e) => setPrivacy(e.target.value)} - sx={{ - '& .MuiRadio-root': { - color: 'var(--darkGray-HC-white)', - '&.Mui-checked': { - color: 'var(--turquoise-HC-white)', - }, - }, - }} - > - <FormControlLabel - value="public" - control={<Radio />} - label="Coleção Pública" - sx={{ - '& .MuiFormControlLabel-label': { - color: 'var(--darkGray-HC-white)', - }, - }} - /> - <FormControlLabel - value="private" - control={<Radio />} - label="Coleção Privada" - sx={{ - '& .MuiFormControlLabel-label': { - color: 'var(--darkGray-HC-white)', - }, - }} - /> - </RadioGroup> - </FormLabel> - {collectionData.collection_items.length ? ( - <> - <p className="text-lg text-darkGray-HC-white font-bold mb-3 mt-4">Remover da coleção:</p> - <div className="flex flex-col gap-2"> - {collectionData?.collection_items?.map((item) => ( - <label - key={item.id} - className="flex items-center gap-2 cursor-pointer text-darkGray-HC-white" - > - <input - type="checkbox" - checked={selectedResources.includes(item.id)} - onChange={() => handleToggle(item.id)} - className="w-4 h-4 border border-darkGray-HC-white bg-white-hc-dark accent-darkTurquoise-HC-white" - /> - <span>{item.collectionable?.name}</span> - </label> - ))} - </div> - </> - ) : null} - </div> - <div className="sticky bottom-0 bg-ice-HC-dark pt-2"> - <div className="flex flex-row-reverse gap-2 justify-center space-x-2"> - <button - className="border text-sm p-2 text-white-HC-dark-underline border-ice-HC-dark hover:border-ice-HC-white hover:text-white rounded-lg normal-case h-9 font-bold bg-turquoise-HC-white hover:bg-darkTurquoise-HC-dark" - type="button" // Alterado para "button" - onClick={handleSubmit} // Chama a função diretamente no onClick - > - Salvar Alterações - </button> - <button - className="border text-sm p-2 mr-3 text-darkGray-HC-white-underline border-ice-HC-white rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-lightGray-HC-white hover:text-darkGray-HC-dark-underline" - onClick={() => { - setSelectedResources([]); - onClose(); - }} - > - Cancelar - </button> - </div> - <ModalSuccess open={openSuccess} onClose={() => setOpenSuccess(false)} modalOnClose={onClose} /> - </div> + </RadioGroup> + </FormLabel> + <div className="flex flex-row-reverse justify-center space-x-2"> + <button + className="border text-sm p-2 text-white-HC-dark-underline border-ice-HC-dark hover:border-ice-HC-white hover:text-white rounded-lg normal-case h-9 font-bold bg-turquoise-HC-white hover:bg-darkTurquoise-HC-dark" + type="button" // Alterado para "button" + onClick={handleSubmit} // Chama a função diretamente no onClick + > + Salvar Alterações + </button> + <button + className="border text-sm p-2 text-darkGray-HC-white-underline border-main rounded-lg normal-case h-9 font-bold bg-ice-HC-dark hover:bg-lightGray-HC-dark" + onClick={onClose} + > + Cancelar + </button> + <ModalSuccess open={openSuccess} onClose={() => setOpenSuccess(false)} modalOnClose={onClose} /> + </div> </form> </div> </Modal> diff --git a/src/app/perfil/[id]/components/FollowersCards.js b/src/app/perfil/[id]/components/FollowersCards.js index aff666afc914cfd1650a0c6dfff26fe75a61b5ae..29fcf4aab9e99660a9dfcf39fa02b8f7c4714810 100644 --- a/src/app/perfil/[id]/components/FollowersCards.js +++ b/src/app/perfil/[id]/components/FollowersCards.js @@ -5,7 +5,7 @@ import UsersPageCard from "@/app/components/UsersPageCard"; import { Button } from "@mui/material"; import NotFound from "./NotFound"; import Loading from "@/app/components/Loading"; - +import { authHeaders } from "@/app/handlers/loginHandler"; /** * * @param {Object} props @@ -16,25 +16,15 @@ import Loading from "@/app/components/Loading"; export default function FollowersCards({ id, count }) { const [numberCards, setNumberCards] = useState(0); const [followers, setFollowers] = useState([]); - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); - const expiry = getStoredValue("expiry"); const [got, setGot] = useState(false); useEffect(() => { const fetchData = async () => { try { - const { data } = await mecredApi.get(`/users/${id}/followers?offset=${numberCards}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + const { data } = await mecredApi.get(`public/user/followers/${id}?offset=${numberCards}`, { + headers: authHeaders() }); - setFollowers((prevFollowers) => [...prevFollowers, ...data]); + setFollowers((prevFollowers) => [...prevFollowers, ...(Array.isArray(data) ? data : [])]); setGot(true); } catch (error) { console.error(error); @@ -45,7 +35,7 @@ export default function FollowersCards({ id, count }) { // token changes after each request so if we put as dependecy it will keep changing and make more than one request. // to solve this we probably need useContext. // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id, numberCards, client, uid, expiry]); + }, [id, numberCards]); const toggleContent = () => { setNumberCards((prevNumberCards) => prevNumberCards + 12); diff --git a/src/app/perfil/[id]/components/FollowingCards.js b/src/app/perfil/[id]/components/FollowingCards.js index a70e0bd343bd3cfb2e50f63362ce8f0e24bec4f0..37e3408edd5ebc5f5d658f2e074dd7ff83df86e7 100644 --- a/src/app/perfil/[id]/components/FollowingCards.js +++ b/src/app/perfil/[id]/components/FollowingCards.js @@ -5,6 +5,7 @@ import UsersPageCard from "@/app/components/UsersPageCard"; import { Button } from "@mui/material"; import NotFound from "./NotFound"; import Loading from "@/app/components/Loading"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @@ -21,19 +22,14 @@ export default function FollowingCards({ id, count }) { const uid = getStoredValue("uid"); const expiry = getStoredValue("expiry"); const [got, setGot] = useState(false); + useEffect(() => { const fetchData = async () => { try { - const { data } = await mecredApi.get(`/users/${id}/following/User?offset=${numberCards}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + const { data } = await mecredApi.get(`public/user/follows/${id}`, { + headers: authHeaders() }); - setFollowing((prevFollowing) => [...prevFollowing, ...data]); + setFollowing((prevFollowing) => [...prevFollowing, ...(Array.isArray(data) ? data : [])]); setGot(true); } catch (error) { console.error(error); diff --git a/src/app/perfil/[id]/components/GroupButton.js b/src/app/perfil/[id]/components/GroupButton.js index cfa838fe776be69c505541989c6abe5080436a91..6b1014d1ad8097661b911f0f2596c1106e03a718 100644 --- a/src/app/perfil/[id]/components/GroupButton.js +++ b/src/app/perfil/[id]/components/GroupButton.js @@ -42,8 +42,8 @@ export default function GroupButton({ profileData, idLogin }) { * Fetch de seguir ou parar de seguir outro usuário */ const followHandler = () => { - if (!loginBarrier()) - return + // if (!loginBarrier()) + // return mecredApi.put( `users/${profileData["id"]}/follow/`, {}, diff --git a/src/app/perfil/[id]/components/MedalAchievements.js b/src/app/perfil/[id]/components/MedalAchievements.js index 293427e891b54a071f31ff9c2c253ae62cb42a73..406b17972a509df9c1d52bd515cb9bf0fe01707e 100644 --- a/src/app/perfil/[id]/components/MedalAchievements.js +++ b/src/app/perfil/[id]/components/MedalAchievements.js @@ -7,19 +7,27 @@ export default function MedalAchievements({ items }) { return ( <div className="flex flex-row w-1/3 max-sm:w-full justify-end max-sm:justify-center xl:gap-x-6 max-sm:gap-x-2 md:gap-x-2 md:max-xl:ml-[80px] xl:mr-10 max-sm:mb-5"> - {items?.map((avatar, index) => - avatar.being_used && ( - <div key={index} className="flex shrink-0 flex-row relative"> - <img src="/medalha-exibicao-da-conquista.svg" alt="medal" className=" flex w-[69px] h-[92px] max-sm:w-[50px] invertLogo-HC-white" /> - <Avatar - className="w-[57px] h-[55px] max-sm:w-[40px] max-sm:h-[40px] max-sm:top-4 max-sm:right absolute top-1.5 right-1.5 " - key={avatar.item ? avatar.name : null} - alt={avatar.item ? avatar.item.name : avatar.name} - src={mecredURL + (avatar.item ? avatar.item.image : null)} - /> - </div> + {(items?.length ?? 0) > 0 ? ( + items.map((avatar, index) => + avatar.is_active && ( + <div key={index} className="flex shrink-0 flex-row relative"> + <img + src="/medalha-exibicao-da-conquista.svg" + alt="medal" + className="flex w-[69px] h-[92px] max-sm:w-[50px] invertLogo-HC-white" + /> + <Avatar + className="w-[57px] h-[55px] max-sm:w-[40px] max-sm:h-[40px] max-sm:top-4 max-sm:right absolute top-1.5 right-1.5" + key={avatar.item ? avatar.name : null} + alt={avatar.item ? avatar.item.name : avatar.name} + src={mecredURL + (avatar.item?.image ?? '')} + /> + </div> + ) ) + ) : ( + <div></div> )} </div> - ); + ) } diff --git a/src/app/perfil/[id]/components/ProfileCollections.js b/src/app/perfil/[id]/components/ProfileCollections.js index 2502ee6e221e7d8e607966e7c3494bad24c4a641..0e0adfabb1dc4b91f9344d196e7a6259e1528a07 100644 --- a/src/app/perfil/[id]/components/ProfileCollections.js +++ b/src/app/perfil/[id]/components/ProfileCollections.js @@ -12,8 +12,7 @@ import { Modal } from "@mui/material"; import EditCollectionModal from "./EditCollectionModal"; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import ShareButton from "@/app/components/ShareButton"; -import EditCollectionButton from "./EditCollectionButton"; -import { useCallback } from "react"; +import { authHeaders } from "@/app/handlers/loginHandler"; const ModalSucess = ({ open, onClose, modalOnClose }) => { return ( @@ -49,9 +48,6 @@ export default function ProfileCollections({ id, idLogin }) { const [numberCards, setNumberCards] = useState(4) const [deleteOpen, setDeleteOpen] = useState(false) const [colToDelete, setColToDelete] = useState(0) - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); const [got, setGot] = useState(false) const [cardsPerRow, setCardsPerRow] = useState(0); @@ -69,36 +65,37 @@ export default function ProfileCollections({ id, idLogin }) { }, [got]); - const fetchCollections = useCallback(async (idParam = id) => { - if (idLogin === idParam) { - const { data, headers } = await mecredApi.get( - `/users/${idParam}/collections?limit=${numberCards}`, - { - headers: { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }, + useEffect(() => { + const fetchCollections = async (id) => { + try { + if (idLogin === Number(id)) { + const { data, headers } = await mecredApi.get(`api/collections/${id}/all-collections`, { + headers: authHeaders(), + }); + + /** + * Além de setar as coleções, indica o número total de coleções + * Isso facilita na verificação + */ + setTotalCount(headers["x-total-count"]); + setCollections(data.fullCollections); + setGot(true); + } else { + const { data, headers } = await mecredApi.get(`public/collections/${id}/collections`); + + setTotalCount(headers["x-total-count"]); + setCollections(data); + console.log(data); + setGot(true); } - ); - setTotalCount(headers["x-total-count"]); - setCollections(data); - setGot(true); - } else { - const { data, headers } = await mecredApi.get( - `/users/${idParam}/collections?limit=${numberCards}` - ); - setTotalCount(headers["x-total-count"]); - setCollections(data); - setGot(true); - } - }, [id, idLogin, numberCards, token, client, uid]); + } catch (error) { + console.error("Erro ao buscar coleções:", error); + } + }; - useEffect(() => { fetchCollections(id); - }, [fetchCollections, id]); + }, [id, numberCards, idLogin, deleted]); + /** * Inicialmente sempre vai aparecer 4 coleções para o usuário (se ele tiver mais que 4). @@ -112,13 +109,7 @@ export default function ProfileCollections({ id, idLogin }) { e.preventDefault(); const url = `/collections/${colToDelete}`; - const headers = { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }; + const headers = authHeaders(); try { await mecredApi.delete(url, { headers }); // Passando o objeto 'headers' corretamente @@ -180,11 +171,11 @@ export default function ProfileCollections({ id, idLogin }) { <ModalSucess open={modalOpen} onClose={() => setModalOpen(false)} modalOnClose={() => setDeleted(!deleted)} /> - {collections.length === 0 ? ( + {collections?.length === 0 ? ( <NotFound name="coleções" /> ) : ( <div className="justify-center mt-5"> - {collections.map((item, index) => ( + {collections?.map((item, index) => ( <div key={index} className="bg-white-HC-dark outline outline-1 outline-ice-HC-white mb-10 pt-6 px-4 rounded-2xl relative"> {/* Cabeçalho com título, autor e botões */} @@ -199,7 +190,12 @@ export default function ProfileCollections({ id, idLogin }) { </Link> </p> <p className="ml-1 text-darkGray-HC-white-underline"> - por <Link className="hover:underline" href={`/perfil/${item.owner.id}`}>{item.owner.name}</Link> + por {item.owner?.id && ( + <Link className="hover:underline" href={`/perfil/${item.owner.id}`}> + {item.owner.name} + </Link> + )} + </p> </div> @@ -249,7 +245,7 @@ export default function ProfileCollections({ id, idLogin }) { {/* Cartões de coleção abaixo */} <div className="mt-4"> - <GroupCardsCollections data={item.collection_items} cardsPerRow={cardsPerRow} /> + <GroupCardsCollections collectionId={item.id} cardsPerRow={cardsPerRow} /> </div> </div> ))} diff --git a/src/app/perfil/[id]/components/ProfileComplaints.js b/src/app/perfil/[id]/components/ProfileComplaints.js index f679e9b0ee782af886a8c6deca1fb90a8bdd4a40..8d919778e81a81408eb1eb7cdfe1e2758809104d 100644 --- a/src/app/perfil/[id]/components/ProfileComplaints.js +++ b/src/app/perfil/[id]/components/ProfileComplaints.js @@ -4,6 +4,7 @@ import mecredApi from "@/axiosConfig"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import Loading from "@/app/components/Loading"; import CardsComplaints from "./CardsComplaints"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @returns Cards dos recursos para homologação (Só aparece para curadores) @@ -25,14 +26,8 @@ export default function ProfileComplaints({ id }) { useEffect(() => { const fetchComplaints = async (id) => { await mecredApi - .get(`/learning_objects_complaints?offset=0&limit=${numberCards}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + .get(`api/complaint/resources`, { + headers: authHeaders() }) .then(({ data, headers }) => { setComplaints(data); @@ -57,7 +52,7 @@ export default function ProfileComplaints({ id }) { {got ? ( <Card className='p-3 my-10 rounded-md min-w-[200px] min-h-[180px] bg-white-HC-dark shadow-none '> <div className="flex content flex-wrap justify-center"> - {complaints.map((item, i) => ( + {complaints?.map((item, i) => ( i % 2 === 0 && ( <div key={i} className="flex"> {complaints[i] && <CardsComplaints item={complaints[i]} tag={true} />} diff --git a/src/app/perfil/[id]/components/ProfileHomologation.js b/src/app/perfil/[id]/components/ProfileHomologation.js index e7e8e292db39037983ffb4f2cfb81c6a71356cbe..d1b4f05c0f6b318f13cd50b05a37fe16dbf04fbd 100644 --- a/src/app/perfil/[id]/components/ProfileHomologation.js +++ b/src/app/perfil/[id]/components/ProfileHomologation.js @@ -4,6 +4,7 @@ import mecredApi from "@/axiosConfig"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import CardsHomologation from "./CardsHomologation"; import Loading from "@/app/components/Loading"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @returns Cards dos recursos para homologação (Só aparece para curadores) @@ -22,16 +23,11 @@ export default function ProfileHomologation({ id }) { useEffect(() => { const fetchHomologation = async (id) => { await mecredApi - .get(`/submissions/all_users_submissions/${id}?offset=${numberCards}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + .get(`api/homologation/all`, { + headers: authHeaders() }) .then(({ data, headers }) => { + console.log(data) setHomologated(prevData => [...prevData, ...data]); setTotalCount(Number(headers["x-total-count"])) setGot(true) @@ -53,7 +49,7 @@ export default function ProfileHomologation({ id }) { {got ? ( <Card className='p-3 my-10 rounded-md min-w-[200px] min-h-[180px] bg-white-HC-dark shadow-none '> <div className="flex content flex-wrap justify-center"> - {homologated.map((item, i) => ( + {homologated?.map((item, i) => ( <CardsHomologation item={item} key={i} /> ))} </div> diff --git a/src/app/perfil/[id]/components/ProfileItens.js b/src/app/perfil/[id]/components/ProfileItens.js index 389e6c57fbfc6c287c548b5539c92101c161206a..efeb65d13e2a05c77386e6a400925465b42f2683 100644 --- a/src/app/perfil/[id]/components/ProfileItens.js +++ b/src/app/perfil/[id]/components/ProfileItens.js @@ -11,318 +11,342 @@ import Link from 'next/link'; import Image from 'next/image'; export default function ProfileItens({ profileData, setItems, items, store, type }) { - const [showDescription, setShowDescription] = useState(null); - const [equip, setEquip] = useState(null); - const [errorEquip, setErrorEquip] = useState(false); - const [fullEquiped, setFullEquiped] = useState(false); - const [buyItem, setBuyItem] = useState(null); - const [arrayAcquired, setArrayAcquired] = useState(new Array(store.length).fill(0)); - - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); + const [showDescription, setShowDescription] = useState(null); + const [equip, setEquip] = useState(null); + const [errorEquip, setErrorEquip] = useState(false); + const [fullEquiped, setFullEquiped] = useState(false); + const [buyItem, setBuyItem] = useState(null); + const [arrayAcquired, setArrayAcquired] = useState(new Array(store.length).fill(0)); - useEffect(() => { - const handlePurchase = () => { - setArrayAcquired(prev => { - const newArray = [...prev]; - for (let i = 0; i < store.length; i++) - newArray[i] = items.some((obj) => obj.item?.id === store[i]?.id) ? 1 : 0; - return newArray; - }); - } - - handlePurchase(); - }, [items, store]); - + useEffect(() => { + const handlePurchase = () => { + setArrayAcquired(prev => { + const newArray = [...prev]; + for (let i = 0; i < store.length; i++) + newArray[i] = items.some((obj) => obj.item?.id === store[i]?.id) ? 1 : 0; + return newArray; + }); + } - const iconEggs = <Image - src="/gema.svg" - alt="gema icon" - width={15} - height={10} - className="inline mx-1 mb-1 invertLogo-HC-white" - /> + handlePurchase(); + }, [items, store]); + console.log(arrayAcquired); - const ModalBuyItem = ({ name, price, open, onClose, index, id }) => { - const restEggs = profileData["points"] - price; - - return ( - <Modal - open={open} - onClose={onClose} - className="grid place-items-center m-5" - slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", - }, - }, - }} - > - <div className="bg-ice-HC-dark outline outline-1 outline-ice-HC-white p-5 rounded-lg"> - <div className='flex justify-end'> - <IconButton onClick={onClose} className='group hover:bg-ice-HC-white'> - <CloseIcon fontSize='large' className='text-darkGray-HC-white group-hover:text-darkGray-HC-dark' /> - </IconButton> - </div> - <div className="flex flex-col gap-5 justify-center "> - <p className="font-bold text-xl text-darkGray-HC-white">Deseja realmente comprar este item?</p> - <p className="font-bold text-darkGray-HC-white">Esta compra não envolve nenhum dinheiro real.</p> - <p className="inline text-darkGray-HC-white">O item que você deseja comprar, - <span className="font-bold"> {name}</span>, custa - <span className="font-bold text-turquoise-HC-white "> {iconEggs} {price}</span> gemas. Você possui - <span className="font-bold text-turquoise-HC-white "> {iconEggs} {profileData["points"]}</span> gemas. - </p> - {restEggs < 0 ? ( - <p className="font-bold text-darkGray-HC-white">Você não possui gemas o suficiente para comprar este item.</p> - ) : ( - <> - <p className="inline text-darkGray-HC-white">Comprar este item lhe deixará com - <span className="font-bold text-turquoise-HC-white "> {iconEggs} {restEggs} </span> - gemas. - </p> - </> - )} - </div> - <div className="flex justify-end mt-4"> - <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Cancelar</button> - {restEggs >= 0 && ( - <button onClick={() => { - handlePostRequestStore(id, index); - onClose(); // Fecha o modal - }} className="bg-turquoise-HC-white text-white-HC-dark-underline hover:text-white-HC-underline p-2 rounded-lg hover:bg-darkTurquoise-HC-dark ml-4 outline outline-1 outline-ice-HC-white">Comprar</button> - )} - </div> - </div> - </Modal> - ); - } + const iconEggs = <Image + src="/gema.svg" + alt="gema icon" + width={15} + height={10} + className="inline mx-1 mb-1 invertLogo-HC-white" + /> - const ModalFullEquiped = ({ open, onClose }) => { - return ( - <Modal - open={open} - onClose={onClose} - className="grid place-items-center m-5" - slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", - }, - }, - }} - > - <div className="bg-ice-HC-dark outline outline-1 outline-ice-HC-white p-5 rounded-lg"> - <div className='flex justify-end'> - <IconButton onClick={onClose} className='group hover:bg-ice-HC-white'> - <CloseIcon fontSize='large' className='text-darkGray-HC-white group-hover:text-darkGray-HC-dark' /> - </IconButton> - </div> - <div className="flex flex-col justify-center "> - <p className="pt-3 text-xl text-darkGray-HC-white">Você só pode equipar até 3 insígnias simultaneamente</p> - </div> - <div className="flex justify-end mt-4"> - <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Fechar</button> - </div> - </div> - </Modal> - ) - } - const ModalErrorEquip = ({ open, onClose }) => { - return ( - <Modal - open={open} - onClose={onClose} - className="grid place-items-center m-5" - slotProps={{ - backdrop: { - sx: { - backgroundColor: "rgba(0, 0, 0, 0.3)", - }, - }, - }} - > - <div className="outline outline-1 outline-ice-HC-white bg-ice-HC-dark p-5 rounded-lg"> - <div className="flex flex-col items-center justify-center "> - <p className="pt-3 text-xl text-darkGray-HC-white "> - Erro na comunicação com o servidor. - Por favor entre em contato com o suporte: - </p> - <a href="/contato" className="flex mt-4 w-24 bg-turquoise text-md text-white-HC-dark-underline p-2 px-6 justify-center rounded-lg hover:bg-turquoise-hover"> - Contato - </a> - </div> - <div className="flex justify-end mt-4"> - <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Fechar</button> - </div> - </div> - </Modal> - ) - } + const ModalBuyItem = ({ name, price, open, onClose, index, id }) => { + const restEggs = profileData["points"] - price; - const handlePostRequestStore = async (id, index) => { - try{ - await mecredApi - .post(`/users/purchase_item`, - {"item_id": id}, - { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) - - - setArrayAcquired(prev => { - const newArray = [...prev]; // Cria uma cópia do array - newArray[index] = 1; - return newArray; // Retorna o novo array atualizado - }) - - await mecredApi - .get(`/users/${profileData.id}/items/?item_type=badge&limit=1000`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) - .then(({ data }) => { - setItems(data); - }) - .catch(() => setError(true)) + return ( + <Modal + open={open} + onClose={onClose} + className="grid place-items-center m-5" + slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", + }, + }, + }} + > + <div className="bg-ice-HC-dark outline outline-1 outline-ice-HC-white p-5 rounded-lg"> + <div className='flex justify-end'> + <IconButton onClick={onClose} className='group hover:bg-ice-HC-white'> + <CloseIcon fontSize='large' className='text-darkGray-HC-white group-hover:text-darkGray-HC-dark' /> + </IconButton> + </div> + <div className="flex flex-col gap-5 justify-center "> + <p className="font-bold text-xl text-darkGray-HC-white">Deseja realmente comprar este item?</p> + <p className="font-bold text-darkGray-HC-white">Esta compra não envolve nenhum dinheiro real.</p> + <p className="inline text-darkGray-HC-white">O item que você deseja comprar, + <span className="font-bold"> {name}</span>, custa + <span className="font-bold text-turquoise-HC-white "> {iconEggs} {price}</span> gemas. Você possui + <span className="font-bold text-turquoise-HC-white "> {iconEggs} {profileData["points"]}</span> gemas. + </p> + {restEggs < 0 ? ( + <p className="font-bold text-darkGray-HC-white">Você não possui gemas o suficiente para comprar este item.</p> + ) : ( + <> + <p className="inline text-darkGray-HC-white">Comprar este item lhe deixará com + <span className="font-bold text-turquoise-HC-white "> {iconEggs} {restEggs} </span> + gemas. + </p> + </> + )} + </div> + <div className="flex justify-end mt-4"> + <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Cancelar</button> + {restEggs >= 0 && ( + <button onClick={() => { + handlePostRequestStore(id, index); + onClose(); // Fecha o modal + }} className="bg-turquoise-HC-white text-white-HC-dark-underline hover:text-white-HC-underline p-2 rounded-lg hover:bg-darkTurquoise-HC-dark ml-4 outline outline-1 outline-ice-HC-white">Comprar</button> + )} + </div> + </div> + </Modal> + ); + } + const ModalFullEquiped = ({ open, onClose }) => { + return ( + <Modal + open={open} + onClose={onClose} + className="grid place-items-center m-5" + slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", + }, + }, + }} + > + <div className="bg-ice-HC-dark outline outline-1 outline-ice-HC-white p-5 rounded-lg"> + <div className='flex justify-end'> + <IconButton onClick={onClose} className='group hover:bg-ice-HC-white'> + <CloseIcon fontSize='large' className='text-darkGray-HC-white group-hover:text-darkGray-HC-dark' /> + </IconButton> + </div> + <div className="flex flex-col justify-center "> + <p className="pt-3 text-xl text-darkGray-HC-white">Você só pode equipar até 3 insígnias simultaneamente</p> + </div> + <div className="flex justify-end mt-4"> + <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Fechar</button> + </div> + </div> + </Modal> + ) + } - } catch (error) { - setErrorEquip(true); - console.error("Erro ao fazer a requisição:", error); + const ModalErrorEquip = ({ open, onClose }) => { + return ( + <Modal + open={open} + onClose={onClose} + className="grid place-items-center m-5" + slotProps={{ + backdrop: { + sx: { + backgroundColor: "rgba(0, 0, 0, 0.3)", + }, + }, + }} + > + <div className="outline outline-1 outline-ice-HC-white bg-ice-HC-dark p-5 rounded-lg"> + <div className="flex flex-col items-center justify-center "> + <p className="pt-3 text-xl text-darkGray-HC-white "> + Erro na comunicação com o servidor. + Por favor entre em contato com o suporte: + </p> + <a href="/contato" className="flex mt-4 w-24 bg-turquoise text-md text-white-HC-dark-underline p-2 px-6 justify-center rounded-lg hover:bg-turquoise-hover"> + Contato + </a> + </div> + <div className="flex justify-end mt-4"> + <button onClick={onClose} className="bg-ice-HC-dark p-2 rounded-lg hover:bg-ice-HC-dark-hover text-darkGray-HC-white-underline mr-1">Fechar</button> + </div> + </div> + </Modal> + ) + } + + const handlePostRequestStore = async (id, index) => { + try { + await mecredApi + .post(`/users/purchase_item`, + { "item_id": id }, + { + headers: { + 'access-token': token, + 'token-type': 'Bearer', + 'client': client, + 'uid': uid, + 'Expires': 0 } + }) + + + setArrayAcquired(prev => { + const newArray = [...prev]; // Cria uma cópia do array + newArray[index] = 1; + return newArray; // Retorna o novo array atualizado + }) + + await mecredApi + .get(`/users/${profileData.id}/items/?item_type=badge&limit=1000`, { + headers: { + 'access-token': token, + 'token-type': 'Bearer', + 'client': client, + 'uid': uid, + 'Expires': 0 + } + }) + .then(({ data }) => { + setItems(data); + }) + .catch(() => setError(true)) + + + } catch (error) { + setErrorEquip(true); + console.error("Erro ao fazer a requisição:", error); } + } - {/* + {/* id: id do item a ser alterado url: É a variação se vai equipar ou desequipar tal item index: A posição do item na lista de itens do json */} - const handlePostRequestItem = async (id, url, index) => { + const handlePostRequestItem = async (id, url, index) => { - const result = items.filter((item) => item.being_used === true); + const result = items.filter((item) => item.being_used === true); - if (result.length === 3 && url === "equip_item" ) { - setFullEquiped(true); - return; - } - - items[index].being_used = !items[index].being_used; - setItems([...items]); - + if (result.length === 3 && url === "equip_item") { + setFullEquiped(true); + return; + } - try{ - await mecredApi - .post(`/users/${url}`, - {"item_id": id}, - { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) - } catch (error) { - setErrorEquip(true); - console.error("Erro ao fazer a requisição:", error); - } + items[index].being_used = !items[index].being_used; + setItems([...items]); + console.log(profileData["user_items"]); + + try { + await mecredApi + .post(`/users/${url}`, + { "item_id": id }, + { + headers: { + 'access-token': token, + 'token-type': 'Bearer', + 'client': client, + 'uid': uid, + 'Expires': 0 + } + }) + } catch (error) { + setErrorEquip(true); + console.error("Erro ao fazer a requisição:", error); } - - return ( - <> - <ModalErrorEquip open={errorEquip} onClose={() => setErrorEquip(false)} /> - <ModalFullEquiped open={fullEquiped} onClose={() => setFullEquiped(false)} /> - {/* Cards de itens do usuário*/} - {(type === "item" ? items : store)?.map((e, index) => { - - return ( - <div key={index} className="relative flex flex-c bg-ice-HC-dark w-[250px] h-[370px] justify-center outline outline-1 outline-ice-HC-white rounded-2xl m-2 text-center"> - {/* Parte de trás (detalhes) */} - {showDescription == index && ( - <div className="absolute inset-0 flex flex-col gap-5 text-[100%] text-darkGray-HC-white bg-ice-HC-dark bg-opacity-90 p-6 rounded-2xl pt-8 z-10 text-left"> - <div className="h-[270px] flex flex-col gap-6"> - <p className="font-bold">{e.item?.name || e.name}</p> - <p className="font-light overflow-y-auto scrollbar-none">{e.item?.description || e.description}</p> - </div> - - <button - className="absolute bottom-0 pb-8 text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white text-left" - onClick={() => setShowDescription(null)} - > - Voltar - </button> - </div> - )} + } - <div className="relative flex flex-col text-xs pt-8" > - <div className="flex justify-center items-center text-darkGray-HC-white-underline pb-4"> - <Avatar - alt={e.item?.name || e.name} - src={mecredURL + (e.item?.image || e.image)} - sx={{ width: 145, height: 145 }} - /> - </div> - <div className="flex flex-col justify-center text-base items-center"> - {(e.item === undefined) ? ( - <> - <h1 className="flex text-lg text-darkGray-HC-white text-[100%] font-bold px-6 items-center min-h-[40px]"> - {e.item?.name || e.name} - </h1> - <button className="text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white pb-2" onClick={() => setShowDescription(index)}>Ver mais</button> - <div className='flex flex-wrap gap-x-4'> - <h1 className='flex text-darkGray-HC-white font-bold gap-x-1'>{iconEggs}{e.price}</h1> - </div> - <ModalBuyItem - name={e.name} - price={e.price} - open={buyItem === e.id} - onClose={() => setBuyItem(null)} - index={index} - id={e.id} - /> - <button className="flex h-[43px] w-[158px] justify-center items-center bg-lightGray-HC-dark hover:bg-darkGray-HC-white rounded-[10px] outline outline-1 outline-ice-HC-white font-bold text-darkGray-HC-white-underline hover:text-white-HC-dark-underline mt-2" - onClick={() => !arrayAcquired[index] && setBuyItem(e.id)} - > - {arrayAcquired[index] ? "Adquirido" : "Comprar"} - </button> - </> - ) : ( - <> - <h1 className="flex text-lg text-darkGray-HC-white text-[100%] font-bold px-6 items-center min-h-[60px]"> - {e.item?.name || e.name} - </h1> - <button className="text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white" onClick={() => setShowDescription(index)}>Ver mais</button> - <button className="flex h-[43px] w-[158px] justify-center items-center bg-lightGray-HC-dark hover:bg-darkGray-HC-white rounded-[10px] outline outline-1 outline-ice-HC-white font-bold text-darkGray-HC-white hover:text-white-HC-dark-underline mt-4" - onClick={() => handlePostRequestItem(e.item?.id || null, e.being_used ? "unequip_item" : "equip_item", index)} - > - {e.being_used ? "Desequipar" : "Equipar"} - </button> - </> - )} - - </div> - </div> - </div> - ); - })} - </> - ); -} + return ( + <> + <ModalErrorEquip open={errorEquip} onClose={() => setErrorEquip(false)} /> + <ModalFullEquiped open={fullEquiped} onClose={() => setFullEquiped(false)} /> + {/* Cards de itens do usuário */} + {(type === "item" ? items : store)?.length > 0 ? ( + (type === "item" ? items : store).map((e, index) => { + return ( + <div + key={index} + className="relative flex flex-c bg-ice-HC-dark w-[250px] h-[370px] justify-center outline outline-1 outline-ice-HC-white rounded-2xl m-2 text-center" + > + {/* Parte de trás (detalhes) */} + {showDescription == index && ( + <div className="absolute inset-0 flex flex-col gap-5 text-[100%] text-darkGray-HC-white bg-ice-HC-dark bg-opacity-90 p-6 rounded-2xl pt-8 z-10 text-left"> + <div className="h-[270px] flex flex-col gap-6"> + <p className="font-bold">{e.item?.name || e.name}</p> + <p className="font-light overflow-y-auto scrollbar-none"> + {e.item?.description || e.description} + </p> + </div> + <button + className="absolute bottom-0 pb-8 text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white text-left" + onClick={() => setShowDescription(null)} + > + Voltar + </button> + </div> + )} + + <div className="relative flex flex-col text-xs pt-8"> + <div className="flex justify-center items-center text-darkGray-HC-white-underline pb-4"> + <Avatar + alt={e.item?.name || e.name} + src={mecredURL + (e.item?.image || e.image)} + sx={{ width: 145, height: 145 }} + /> + </div> + <div className="flex flex-col justify-center text-base items-center"> + {e.item === undefined ? ( + <> + <h1 className="flex text-lg text-darkGray-HC-white text-[100%] font-bold px-6 items-center min-h-[40px]"> + {e.item?.name || e.name} + </h1> + <button + className="text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white pb-2" + onClick={() => setShowDescription(index)} + > + Ver mais + </button> + <div className="flex flex-wrap gap-x-4"> + <h1 className="flex text-darkGray-HC-white font-bold gap-x-1"> + {iconEggs} + {e.price} + </h1> + </div> + <ModalBuyItem + name={e.name} + price={e.price} + open={buyItem === e.id} + onClose={() => setBuyItem(null)} + index={index} + id={e.id} + /> + <button + className="flex h-[43px] w-[158px] justify-center items-center bg-lightGray-HC-dark hover:bg-darkGray-HC-white rounded-[10px] outline outline-1 outline-ice-HC-white font-bold text-darkGray-HC-white-underline hover:text-white-HC-dark-underline mt-2" + onClick={() => + !arrayAcquired[index] && setBuyItem(e.id) + } + > + {arrayAcquired[index] ? "Adquirido" : "Comprar"} + </button> + </> + ) : ( + <> + <h1 className="flex text-lg text-darkGray-HC-white text-[100%] font-bold px-6 items-center min-h-[60px]"> + {e.item?.name || e.name} + </h1> + <button + className="text-turquoise-HC-white-underline hover:text-darkTurquoise-HC-white" + onClick={() => setShowDescription(index)} + > + Ver mais + </button> + <button + className="flex h-[43px] w-[158px] justify-center items-center bg-lightGray-HC-dark hover:bg-darkGray-HC-white rounded-[10px] outline outline-1 outline-ice-HC-white font-bold text-darkGray-HC-white hover:text-white-HC-dark-underline mt-4" + onClick={() => + handlePostRequestItem( + e.item?.id || null, + e.being_used ? "unequip_item" : "equip_item", + index + ) + } + > + {e.being_used ? "Desequipar" : "Equipar"} + </button> + </> + )} + </div> + </div> + </div> + ); + }) + ) : <div></div>} + </> + ); +} \ No newline at end of file diff --git a/src/app/perfil/[id]/components/ProfileResources.js b/src/app/perfil/[id]/components/ProfileResources.js index 4b791740076dd4d27830312b80d935081ae13820..2b5d189d3633da46749e41121635ad144419964a 100644 --- a/src/app/perfil/[id]/components/ProfileResources.js +++ b/src/app/perfil/[id]/components/ProfileResources.js @@ -6,6 +6,7 @@ import { getStoredValue } from "@/app/handlers/localStorageHandler"; import { Button, Card } from "@mui/material"; import NotFound from "./NotFound"; import Loading from "@/app/components/Loading"; +import { authHeaders } from "@/app/handlers/loginHandler"; /** * @returns Recursos do usuário @@ -19,10 +20,6 @@ export default function ProfileResources({ id, idLogin }) { const [uniqueResources, setUniqueResources] = useState([]) const [resources, setResources] = useState([]) const [homologaResources, setHomologaResources] = useState([]) - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - const expiry = getStoredValue("expiry") const [numberCards, setNumberCards] = useState(0) const [resourcesCount, setResourcesCount] = useState(0) const [homologaCount, setHomologaCount] = useState(0) @@ -34,14 +31,8 @@ export default function ProfileResources({ id, idLogin }) { useEffect(() => { const fetchLearningObjects = async (id) => { await mecredApi - .get(`/users/${id}/learning_objects?offset=${numberCards}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + .get(`public/resource/allResourceByUser/${id}?offset=${numberCards}`, { + headers: authHeaders() }) .then(({ data, headers }) => { setResourcesCount(Number(headers["x-total-count"])); @@ -68,13 +59,7 @@ export default function ProfileResources({ id, idLogin }) { const fetchSubmissions = async (id) => { await mecredApi .get(`/submissions/user_submissions/${id}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + headers: authHeaders() }) .then(({ data }) => { setHomologaResources((prevHomologaResources) => [...prevHomologaResources, ...data]); @@ -90,7 +75,7 @@ export default function ProfileResources({ id, idLogin }) { //só aparece os recursos em homologação se o perfil acessado for o mesmo do logado if (idLogin === id) fetchSubmissions(id); - }, [id, numberCards, token, client, uid, expiry, idLogin]); + }, [id, numberCards, idLogin]); const toggleContent = () => { setNumberCards(numberCards + 12) @@ -112,12 +97,46 @@ export default function ProfileResources({ id, idLogin }) { // Atualiza o estado com os únicos setUniqueResources(remove_duplicates); - setuniqueCount (resourcesCount + homologaCount) } - }, [resources, homologaResources, uniqueCount]); + }, [resources, homologaResources]); + const [imageMap, setImageMap] = useState({}); + const verifyImage = async (id) => { + try { + await mecredApi.get(`public/s3/get/thumbnail/resource/${id}`, { + headers: authHeaders() + }); + return true; // Existe + } catch (error) { + if (error.response && error.response.status === 404) { + return false; // Não existe + } + return false; + } + }; + + useEffect(() => { + const checkImages = async () => { + const map = {}; + + await Promise.all( + resources.map(async (resource) => { + const exists = await verifyImage(resource.id); + map[resource.id] = exists; + }) + ); + + setImageMap(map); + }; + + if (resources.length) { + checkImages(); + } + }, [resources]); + + return ( <> {got ? ( @@ -144,20 +163,39 @@ export default function ProfileResources({ id, idLogin }) { <NotFound name="recursos" /> ) : ( <div className="p-3 my-5 mb-24 mx-5 rounded-md min-w-[200px] min-h-[180px] bg-white-HC-dark "> - <div className="flex flex-wrap justify-center gap-5"> + <div className="flex flex-wrap justify-center"> {uniqueResources.map((resource, index) => ( <Cards id={resource.learning_object?.id || resource?.id} key={index} - title={resource?.name || resource.learning_object?.name} - author={resource.publisher?.name || resource.learning_object?.author} - avatar={resource.publisher?.avatar || resource.submitter?.avatar} - image={resource?.thumbnail || resource.learning_object?.thumbnail} - updated_at={resource?.updated_at || resource.learning_object?.updated_at} - homologa={resource?.status} - setUniqueResources={setUniqueResources} - resource={resource} - profilePage="recurso" + title={resource.name} + author={resource.author} agora a api retorna + avatar={`https://s3.c3sl.ufpr.br/mecredteste/avatar/${id}`} + image={ + imageMap[resource.id] + ? `https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${resource.id}` + : null + } + + type={resource.objectTypeName} + updated_at={resource.updated_at} + /> + ))} + {homologaResources.map((resource, index) => ( + <Cards + id={resource.id} + key={index} + title={resource.name} + author={resource.author} + avatar={`https://s3.c3sl.ufpr.br/mecredteste/avatar/${id}`} + image={ + imageMap[resource.id] + ? `https://s3.c3sl.ufpr.br/mecredteste/thumbnail/resource/${resource.id}` + : null + } + type={resource.objectTypeName} + updated_at={resource.updated_at} + homologa={resource.status} /> ))} </div> diff --git a/src/app/perfil/[id]/components/Stats.js b/src/app/perfil/[id]/components/Stats.js index 38bc6a5f8656b76c27da6ed936fbda9f48dd643c..3975f77ba6074840fa9ba268eb086de2f02b58f8 100644 --- a/src/app/perfil/[id]/components/Stats.js +++ b/src/app/perfil/[id]/components/Stats.js @@ -26,12 +26,12 @@ export default function ProfileStats({ profileData, achievements, progresses, it value: achievements?.length, src: "/conquistas.svg" }, - { - icon: <RotateRightTwoToneIcon fontSize="large"/>, - name: "Progresso:", - value: progresses?.length, - src: "/progressos.svg" - }, + // { + // icon: <RotateRightTwoToneIcon fontSize="large"/>, + // name: "Progresso:", + // value: progresses?.length, + // src: "/progressos.svg" + // }, { icon: <BusinessCenterTwoToneIcon fontSize="large"/>, name: "Itens:", @@ -57,7 +57,11 @@ export default function ProfileStats({ profileData, achievements, progresses, it alt={item.name} /> - <h1 className="flex">{item.name} {item.value}</h1> + <h1 className="flex">{item.name} + {item.value? + item.value : 0 + } + </h1> </div> ))} </div> diff --git a/src/app/perfil/[id]/components/UserCard.js b/src/app/perfil/[id]/components/UserCard.js index 959bd70a3166535959bf5cf1a2f8ee80cebb67f9..fd8b617e9836bfa16f98282996364e46366087cf 100644 --- a/src/app/perfil/[id]/components/UserCard.js +++ b/src/app/perfil/[id]/components/UserCard.js @@ -7,7 +7,7 @@ import { Avatar } from '@mui/material'; import { useEffect, useState } from 'react'; import { getStoredValue } from '@/app/handlers/localStorageHandler'; import mecredApi, { mecredURL } from '@/axiosConfig'; -import { isLoggedIn } from '@/app/handlers/loginHandler'; +import { authHeaders, isLoggedIn, useLoggedIn, userData } from '@/app/handlers/loginHandler'; import Stats from './Stats'; import MedalAchievements from './MedalAchievements'; import AboutCard from "./AboutCard"; @@ -19,7 +19,6 @@ import FollowersCards from "./FollowersCards"; import ProfileComplaints from "./ProfileComplaints"; import ProfileAchievementsMenu from "./ProfileAchievementsMenu"; import { Person } from "@mui/icons-material"; -import { TramSharp } from '@mui/icons-material'; /* Não precisa mais com o Verificado const roles = [ @@ -108,42 +107,28 @@ export default function UserCard({ profileData, idLogin, achievements, progresse const [userVerified, setUserVerified] = useState(false) const [medalActive, setMedalActive] = useState(0) + const [profileInfo, setProfileInfo] = useState("") + const loggedIn = useLoggedIn() /** * faz fetch dos seguidores e seguindo, se não estiver logado não aparece essa informação */ useEffect(() => { - if (!isLoggedIn) + if (!loggedIn) return; + setProfileInfo(userData()["user"]) + console.log(profileData) const fetchFollowers = async () => { - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - const expiry = getStoredValue("expiry") - - await mecredApi.get('/users/' + profileData["id"] + "/followers", { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } - + await mecredApi.get(`public/user/followers/${profileData.id}`, { + headers: authHeaders() }) .then(({ headers }) => setFollowers(Number(headers["x-total-count"]))) .catch((error) => { console.error(error) }) - await mecredApi.get('/users/' + profileData["id"] + "/following/User", { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': expiry - } + await mecredApi.get(`public/user/follows/${profileData.id}`, { + headers: authHeaders() }) .then(({ headers }) => { @@ -155,51 +140,30 @@ export default function UserCard({ profileData, idLogin, achievements, progresse } //Conta quantas medalhas ativas o usuário possui - const countMedalBeingUsed = items?.filter(item => item.being_used).length; - setMedalActive(countMedalBeingUsed) + // const countMedalBeingUsed = items?.filter(item => item.being_used).length; + // setMedalActive(countMedalBeingUsed) fetchFollowers() // Função que análisa se o usuário é considerado "verificado" conforme cargo const verifier = async () => { const all_roles = [ - "admin", "curator", "moderator", - "supervisor", "editor", "partner", "publisher" + "teacher", "admin", "curator", "moderator", + "supervisor", "editor", "partner", "publisher", "submitter" ]; - + // Extrai todos os nomes dos cargos do usuário const user_roles = profileData["roles"].map(role => role.name); - + // Verifica se algum cargo do usuário está na lista de roles permitidos const verifier_user_roles = user_roles.find(role => all_roles.includes(role)); - + if (verifier_user_roles) { setUserVerified(true); } }; verifier() - /** - * Função que faz a tradução conforme os cargos do usuário. - * Busca em `roles` qual respectiva tradução no array. - * A função trata cargos repetidos e, como "submitter" é um cargo de todos os usuários, é retirado da lista. - * Função serve também para verificar se o usuário (no caso, o usuário logado) é curador, para ter permissão de homologação - - NÃO PRECISA MAIS DEVIDO AO VERIFICADO - const itemsRoles = async () => { - let items = [] - for (let i = 0; i < profileData["roles"].length; i++) { - let found = roles.find((element) => profileData["roles"][i]["name"] === element.role) - - if (!items.includes(found.translate) && found.role !== "submitter") items.push(found.translate) - } - setVerifyCurator(items.includes("Curador")) - return items.join(' | '); - } - - itemsRoles() - .then(roles => setTranslateItems(roles)) - */ }, [items, profileData]) @@ -207,59 +171,68 @@ export default function UserCard({ profileData, idLogin, achievements, progresse * Menu de seleção do usuário * Verifica se o usuário está logado para definir as opções */ - let options = [{ name: "Sobre", component: <AboutCard content={profileData["description"]} /> }] - - if (isLoggedIn() && profileData["id"] == idLogin) { + let options = [{ name: "Sobre", component: <AboutCard content={profileData.description} /> }] + if (profileData.id == profileInfo.id) { options.push({ name: "Conquistas", component: <ProfileAchievementsMenu profileData={profileData} achievements={achievements} progresses={progresses} setItems={setItems} items={items} store={store} /> }, - { name: "Meus Recursos", component: <ProfileResources id={profileData["id"]} idLogin={idLogin} /> }, - { name: "Minhas Coleções", component: <ProfileCollections id={profileData["id"]} idLogin={idLogin} /> }, - { name: "Seguidores", component: <FollowersCards id={profileData["id"]} count={followers} /> }, - { name: "Seguindo", component: <FollowingCards id={profileData["id"]} count={following} /> }, + { name: "Meus Recursos", component: <ProfileResources id={profileData.id} idLogin={idLogin} /> }, + { name: "Minhas Coleções", component: <ProfileCollections id={profileData.id} idLogin={idLogin} /> }, + { name: "Seguidores", component: <FollowersCards id={profileData.id} count={followers} /> }, + { name: "Seguindo", component: <FollowingCards id={profileData.id} count={following} /> }, ) if (userVerified) { options.push( - { name: "Homologação", component: <ProfileHomologation id={profileData["id"]} /> }, - { name: "Denúncias", component: <ProfileComplaints id={profileData["id"]} /> } + { name: "Homologação", component: <ProfileHomologation id={profileData.id} /> }, + { name: "Denúncias", component: <ProfileComplaints id={profileData.id} /> } ) } } - else if (profileData["id"] == 0) { + else if (profileData.id == 0) { options.push( - { name: "Recursos", component: <ProfileResources id={profileData["id"]} idLogin={idLogin} /> }, - { name: "Coleções", component: <ProfileCollections id={profileData["id"]} idLogin={idLogin} /> }, + { name: "Recursos", component: <ProfileResources id={profileData.id} idLogin={idLogin} /> }, + { name: "Coleções", component: <ProfileCollections id={profileData.id} idLogin={idLogin} /> }, ) } else { options.push( - { name: "Recursos", component: <ProfileResources id={profileData["id"]} idLogin={idLogin} /> }, - { name: "Coleções", component: <ProfileCollections id={profileData["id"]} idLogin={idLogin} /> }, - { name: "Seguidores", component: <FollowersCards id={profileData["id"]} count={followers} /> }, - { name: "Seguindo", component: <FollowingCards id={profileData["id"]} count={following} /> }, + { name: "Recursos", component: <ProfileResources id={profileData.id} idLogin={idLogin} /> }, + { name: "Coleções", component: <ProfileCollections id={profileData.id} idLogin={idLogin} /> }, + { name: "Seguidores", component: <FollowersCards id={profileData.id} count={followers} /> }, + { name: "Seguindo", component: <FollowingCards id={profileData.id} count={following} /> }, ) } - // const cargo = + const [imageExists, setImageExists] = useState(false); + const imageUrl = `https://s3.c3sl.ufpr.br/mecredteste/avatar/${profileData.id}`; + useEffect(() => { + const checkImage = async () => { + await mecredApi.get(`public/s3/get/avatar/${profileData.id}`, { + headers: authHeaders() + }) + .then(({ headers }) => setImageExists(true)) + .catch((error) => { + setImageExists(false) + }) + }; + + checkImage(); + }, [profileData.id]); + return ( <div className='overflow-y-auto min-h-dvh max-md:mx-[20px] max-md:mb-24 '> <div className='flex flex-col'> <div className='flex max-md:mx-1 mx-0.5 mt-2 max-lg:flex-col rounded-2xl gap-4 max-sm:gap-0 max-lg:items-center bg-white-HC-dark outline outline-1 outline-ice-HC-white' > <div className='p-10 max-sm:p-0 h-full items-center'> - {/*a imagem do usuário tem nomes diferentes no backend se for usuário público ou não */} - {profileData["avatar"] ? - <Avatar - src={mecredURL + profileData["avatar"] + `?t=${new Date().getTime()}`} - sx={{ position: "inherit" }} - alt="Foto de perfil" - className=" h-[276px] w-[276px] " - /> + {imageExists ? + <Avatar src={imageUrl} sx={{ position: "inherit" }} alt="Foto de perfil" className=" h-[276px] w-[276px] " /> : - <div className={`flex items-center justify-center text-8xl font-bold text-ice-HC-dark rounded-full h-[276px] w-[276px] bg-turquoise-HC-white ${getRandomBg(profileData["id"])}`} >{profileData["name"][0]}</div> + <div className={`flex items-center justify-center text-8xl font-bold text-ice-HC-dark rounded-full h-[276px] w-[276px] bg-turquoise-HC-white ${getRandomBg(profileData["id"])}`} >{profileData.name[0]}</div> } + </div> <div className='flex flex-col pt-12 max-lg:pt-2 pb-6 justify-between w-full '> @@ -299,11 +272,12 @@ export default function UserCard({ profileData, idLogin, achievements, progresse </div> {/*Botões de editar perfil e compartilhar */} - <GroupButton profileData={profileData} idLogin={idLogin} /> + <GroupButton profileData={profileData} idLogin={profileInfo.id} /> </div> </div> {/* Stats do usuário*/} + {/* Não há um funcionamento definido, portanto, colocar depois com as rotas estabelecidas */} <Stats profileData={profileData} achievements={achievements} progresses={progresses} items={items} /> @@ -316,7 +290,7 @@ export default function UserCard({ profileData, idLogin, achievements, progresse content={profileData["description"]} optButton={optButton} id={profileData["id"]} - idLogin={idLogin} + idLogin={profileInfo.id} followersCount={followers} followingCount={following} achievements={achievements} diff --git a/src/app/perfil/[id]/page.js b/src/app/perfil/[id]/page.js index 431cb8d6a3e60ae5b2062f55ad00f986ecc779e0..95cd964f7a7b7f1e8d94e7f185488dd99f06ad7c 100644 --- a/src/app/perfil/[id]/page.js +++ b/src/app/perfil/[id]/page.js @@ -4,7 +4,7 @@ import { getStoredValue } from "../../handlers/localStorageHandler"; import { useEffect, useState } from "react"; import Overlay from "../../components/Overlay"; import mecredApi from "@/axiosConfig"; -import { isLoggedIn } from "@/app/handlers/loginHandler"; +import { authHeaders, isLoggedIn, userData } from "@/app/handlers/loginHandler"; import ErrorComponent from "@/app/components/ErrorComponent"; export default function Perfil({ params }) { @@ -15,9 +15,7 @@ export default function Perfil({ params }) { const [progresses, setProgresses] = useState(null); const [error, setError] = useState(false) const [idLogin, setIdLogin] = useState(0) - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") + const optTab = getStoredValue("tab"); @@ -33,7 +31,7 @@ export default function Perfil({ params }) { */ const fetchAchievements = async (id) => { await mecredApi - .get(`/unlocked_achievements/user/${id}?limit=1000`) + .get(`public/userAchievements/${id}/achievements`) .then(({ data }) => { setAchievements(data); }) @@ -42,20 +40,20 @@ export default function Perfil({ params }) { fetchAchievements(params.id); - const fetchProgresses = async (id) => { - await mecredApi - .get(`/user_progresses/${id}`) - .then(({ data }) => { - setProgresses(data); - }) - .catch(() => setError(true)) - } + // const fetchProgresses = async (id) => { + // await mecredApi + // .get(`/user_progresses/${id}`) + // .then(({ data }) => { + // setProgresses(data); + // }) + // .catch(() => setError(true)) + // } - fetchProgresses(params.id) + // fetchProgresses(params.id) const fetchItems = async (id) => { await mecredApi - .get(`/users/${id}/items/?item_type=badge&limit=1000`) + .get(`public/user-items/${id}/items?limit=1000`) .then(({ data }) => { setItems(data); }) @@ -65,44 +63,39 @@ export default function Perfil({ params }) { fetchItems(params.id) if (isLoggedIn()) { - let data = getStoredValue("user_data") - let dataJson = JSON.parse(data); + let data = userData() const fetchUser = async (id) => { await mecredApi - .get(`/users/${id}`, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } + .get(`api/user/${id}`, { + headers: authHeaders() }) .then(({ data }) => { + console.log(data) setProfileData(data); }) .catch(() => setError(true)) } fetchUser(params.id) - setIdLogin(dataJson["id"]) + setIdLogin(data["user"]["id"]) - const fetchStore = async () => { + const fetchStore = async (id) => { await mecredApi - .get(`/items?filter={"state" : "active"}&item_type=badge&unlock_rule=purchase&limit=1000`) + .get(`public/user-items/${id}/items?filter={"state" : "active"}&item_type=badge&unlock_rule=purchase&limit=1000`) .then(({ data }) => { setStore(data) + console.log(data) }) .catch(() => setError(true)) } - fetchStore() + fetchStore(params.id) } else { const fetchUser = async (id) => { await mecredApi - .get(`/users/${id}`) + .get(`api/user/${id}`) .then(({ data }) => { setProfileData(data); }) @@ -112,7 +105,7 @@ export default function Perfil({ params }) { fetchUser(params.id) } - }, [params.id, client, token, uid]) + }, [params]) return ( <> diff --git a/src/app/publicar/components/Form.js b/src/app/publicar/components/Form.js index ea6143284e89275b1af841dc1a5468910ce4a7b3..a7db7cc60017ca6af829f8e911de28180fb2fd9d 100644 --- a/src/app/publicar/components/Form.js +++ b/src/app/publicar/components/Form.js @@ -3,63 +3,29 @@ import { useEffect, useState } from "react"; import UploadForm from "./UploadForm"; import InfoForm from "./InfoForm"; import RevisionForm from "./RevisionForm" -import mecredApi from "@/axiosConfig"; -import { getStoredValue } from "@/app/handlers/localStorageHandler"; import AdvanceNotice from "./AdvanceNotice"; +import { userData } from "@/app/handlers/loginHandler"; export default function Form() { const [step, setStep] = useState(0); const [draft, setDraft] = useState(null); + const [userDatas, setUserDatas] = useState(null); const [authorType, setAuthorType] = useState("a"); const [file, setFile] = useState(null); - const [attachmentId, setAttachmentId] = useState(null); - const [userData, setUserData] = useState(null); const [thumbURL, setThumbURL] = useState(null); const [thumb, setThumb] = useState(null); - - const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); - const payload = {} - useEffect(() => { - const fetchData = async () => { - try { - setUserData(JSON.parse(getStoredValue("user_data"))); - - if (draft && draft.id) { - return; - } - - const response = await mecredApi.post(`/learning_objects/`, payload, { - headers: { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }, - }); - - setDraft(response.data); - } catch (error) { - // Tratar erros, se necessário - console.error('Erro ao enviar dados:', error); - } - }; - - fetchData(); - }, [client, token, uid, draft]); - + setUserDatas(userData()); + }, []) - return ( + return ( userDatas && <> <div className="flex flex-col justify-start w-full mt-15 max-sm:mx-0 max-md:mb-16 overflow-auto scrollbar-none"> - <HorizontalLinearAlternativeLabelStepper step={step} /> - {step === 0 && userData && <InfoForm userData={userData} setStep={setStep} draft={draft} setDraft={setDraft} authorType={authorType} setAuthorType={setAuthorType} />} - {step === 1 && <UploadForm thumbURL={thumbURL} setThumbURL={setThumbURL} thumb={thumb} setThumb={setThumb} setStep={setStep} draft={draft} setDraft={setDraft} file={file} setFile={setFile} attachmentId={attachmentId} setAttachmentId={setAttachmentId} />} - {step === 2 && <RevisionForm setStep={setStep} draft={draft} setDraft={setDraft} />} + <HorizontalLinearAlternativeLabelStepper step={step} /> + {step === 0 && <InfoForm setStep={setStep} draft={draft} setDraft={setDraft} userData={userDatas} authorType={authorType} setAuthorType={setAuthorType} />} + {step === 1 && <UploadForm thumbURL={thumbURL} setThumbURL={setThumbURL} thumb={thumb} setThumb={setThumb} setStep={setStep} draft={draft} setDraft={setDraft} file={file} setFile={setFile} />} + {step === 2 && <RevisionForm setStep={setStep} draft={draft} userData={userDatas} setDraft={setDraft} />} <AdvanceNotice /> </div> </> diff --git a/src/app/publicar/components/InfoForm.js b/src/app/publicar/components/InfoForm.js index fb4978464cbc5af4dc2a61d95a7968ae202d22c3..af788df5c9c4f1850331342c4d5ca19212ac27e6 100644 --- a/src/app/publicar/components/InfoForm.js +++ b/src/app/publicar/components/InfoForm.js @@ -5,48 +5,53 @@ import FormControlLabel from "@mui/material/FormControlLabel"; import SubjectSelection from "./SubjectSelection"; import ObjectTypeSelection from "./ObjectTypeSelection"; import LicenseSelection from "./LicenseSelection"; -import TagInput from "./TagsInput"; +//import TagInput from "./TagsInput"; import AuthorSelection from "./AuthorSelection"; import mecredApi from "@/axiosConfig"; import ModalIncompleteField from "./ModalIncompleteField"; import CancelSubmitModal from "./CancelSubmitModal"; import CustomTextField from "./CustomTextField"; -import CustomCheckbox from "./CustomCheckbox"; - +import { authHeaders } from "@/app/handlers/loginHandler"; +//o codigo de tags esta comentado, pq pode ser que no futuro seja usado export default function InfoForm({ userData, setStep, draft, setDraft, authorType, setAuthorType }) { const [languagesAvailable, setLanguagesAvailable] = useState([]); const [scholarityLevelsAvailable, setScholarityLevelAvailable] = useState([]); const [openModal, setOpenModal] = useState(false); const [filled, setFilled] = useState(false); - const [title, setTitle] = useState(draft?.name ?? ""); const [description, setDescription] = useState(draft?.description ?? ""); - const [tags, setTags] = useState(draft?.tags ?? []); + // const [tags, setTags] = useState(draft?.tags ?? []); + // const [tagInput, setTagInput] = useState(""); const [author, setAuthor] = useState(draft?.author ?? userData.name); - const [tagInput, setTagInput] = useState(""); - const [languages, setLanguages] = useState(draft?.language ?? []); - const [objectTypes, setObjectTypes] = useState(draft?.object_type_f ?? []); - const [scholarityLevelsName, setScholarityLevelsName] = useState(draft?.educational_stages_name ?? []); + const [objectTypes, setObjectTypes] = useState([draft?.object_type_id] ?? []); const [subjects, setSubjects] = useState(draft?.subjects ?? []); const [licenseValue, setLicenseValue] = useState(draft?.license_id ?? ""); - - const [languageIds, setLanguageIds] = useState(draft?.language_ids ?? []); + const [languageIds, setLanguageIds] = useState(draft?.language ?? []); const [scholarityLevelsId, setScholarityLevelsId] = useState(draft?.educational_stages ?? []); - const [openModalCancel, setOpenModalCancel] = useState(false); - - useEffect(() => { - mecredApi.get("/languages") - .then((response) => { setLanguagesAvailable(response.data) }); - }, []); - useEffect(() => { - mecredApi.get("/educational_stages") - .then((response) => { setScholarityLevelAvailable(response.data) }); + const fetchData = async () => { + try { + // faz as duas requisiçoes ao mesmo tempo + //fica mais rapido + const [educationalStageRes, languageRes] = await Promise.all([ + mecredApi.get("public/educationalStage/all"), + mecredApi.get("public/language/all"), + ]); + + setScholarityLevelAvailable(educationalStageRes.data); + setLanguagesAvailable(languageRes.data); + } catch (error) { + console.error("Erro ao buscar dados:", error); + } + }; + + fetchData(); }, []); + const handleTitleChange = (e) => setTitle(e.target.value); const handleDescriptionChange = (e) => setDescription(e.target.value); @@ -59,83 +64,78 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp const handleAuthorChange = (e) => setAuthor(e.target.value); const handleLicenseValueChange = (e) => setLicenseValue(e.target.value); - const handleKeyDown = (e) => { - // Verifica se a tecla pressionada foi "Enter" - if (e.key === "Enter") { - addTagFromInput(); - } - }; - - const handleTagInput = (e) => { - const value = e.target.value; - - // Verifica se o valor contém uma vírgula - if (value.includes(",")) { - addTagFromInput(value); - } else { - setTagInput(value); - } - }; - - const addTagFromInput = (value = tagInput) => { - // Remove a vírgula, se houver, e os espaços - const trimmedValue = value.replace(",", "").trim(); - - // Adiciona a tag se não estiver vazia e não for duplicada - if (trimmedValue !== "" && !tags.includes(trimmedValue)) { - setTags((prevTags) => [...prevTags, trimmedValue]); - } - - // Limpa o campo de entrada - setTagInput(""); - }; - + // const handleKeyDown = (e) => { + // // Verifica se a tecla pressionada foi "Enter" + // if (e.key === "Enter") { + // addTagFromInput(); + // } + // }; - const handleTagDelete = (tag) => { - setTags(tags.filter((t) => t !== tag)); - }; + // const handleTagInput = (e) => { + // const value = e.target.value; + + // // Verifica se o valor contém uma vírgula + // if (value.includes(",")) { + // addTagFromInput(value); + // } else { + // setTagInput(value); + // } + // }; + + // const addTagFromInput = (value = tagInput) => { + // // Remove a vírgula, se houver, e os espaços + // const trimmedValue = value.replace(",", "").trim(); + + // // Adiciona a tag se não estiver vazia e não for duplicada + // if (trimmedValue !== "" && !tags.includes(trimmedValue)) { + // setTags((prevTags) => [...prevTags, trimmedValue]); + // } + + // // Limpa o campo de entrada + // setTagInput(""); + // }; + + //talvez por um parse, pq tem coisa vindo a mais + + // const handleTagDelete = (tag) => { + // setTags(tags.filter((t) => t !== tag)); + // }; const handleLanguagesChange = (e) => { - const lang = JSON.parse(e.target.value); - if (languages.includes(lang.name)) { - setLanguages(l => l.filter((language) => language !== lang.name)); - setLanguageIds(l => l.filter((language) => language !== lang.id)); - } - else { - setLanguages([...languages, lang.name]) - setLanguageIds([...languageIds, lang.id]) - }; + const id = Number(e.target.value); + + setLanguageIds((prev) => e.target.checked ? + [...prev, id] + : prev.filter((languageId) => languageId !== id)); + }; - const handleObjectsTypeChange = (subject) => { - if (objectTypes.includes(subject)) - setObjectTypes(objectTypes.filter((object) => object.id !== subject.id)); - else setObjectTypes([subject]); + const handleObjectsTypeChange = (objectT) => { + if (objectTypes.includes(objectT)) + setObjectTypes(objectTypes.filter((object) => object.id !== objectT.id)); + else setObjectTypes([objectT]); }; const handleScholarityChange = (e) => { - const sl = JSON.parse(e.target.value); - if (scholarityLevelsName.includes(sl.name)) { - setScholarityLevelsName(s => s.filter((educational_stages) => educational_stages !== sl.name)); - setScholarityLevelsId(scholarityLevelsId.filter((educational_stages) => educational_stages !== (JSON.stringify(sl.id)))); - } - else { - setScholarityLevelsName([...scholarityLevelsName, sl.name]); - setScholarityLevelsId([...scholarityLevelsId, (JSON.stringify(sl.id))]); - } + const id = Number(e.target.value); + setScholarityLevelsId((prev) => + e.target.checked + ? [...prev, id] + : prev.filter((scholarityId) => scholarityId !== id) + ); }; const handleSubjectsChange = (subject) => { if (subjects.includes(subject)) - setSubjects(subjects.filter((subj) => subj.id !== subject.id)); + setSubjects(subjects.filter((subj) => subj !== subject)); else { setSubjects([...subjects, subject]) }; }; const handleIncompleteField = () => { - if (!title || !description || !tags.length || !scholarityLevelsId.length || !objectTypes.length - || !languages.length || !subjects.length || !licenseValue) { + if (!title || !description || !scholarityLevelsId.length || !objectTypes.length + || !languageIds.length || !subjects.length || !licenseValue) { setFilled(true) return true } @@ -143,40 +143,63 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp } + //só faz quando vai para a proxima etapa + //para nao gerar muitos drafts vazios + const handleSubmit = async (e) => { + e.preventDefault(); + + if (handleIncompleteField()) { + setOpenModal(true); + return; + } + + const newDraft = { + ...(draft ?? {}), + name: title, + description: description, + // tags: tags, + object_type_id: objectTypes[0], + language: languageIds, + educational_stages: scholarityLevelsId, + subjects: subjects, + license_id: Number(licenseValue), + author: author, + }; - const handleSubmit = (e) => { + //em author colocar o pessoa logada, quando gustavo fazser + + setDraft(newDraft); + + console.log("newDraft:", newDraft) - if (handleIncompleteField()) { - e.preventDefault() - setOpenModal(true) - - } else { - - setDraft((curr) => ({ - ...curr, - name: title, - description: description, - tags: tags, - author: author, - object_type_f: objectTypes, - language_ids: languageIds, - language: languages, - educational_stages: scholarityLevelsId, - educational_stages_name: scholarityLevelsName, - subjects: subjects, - license_id: licenseValue, - })); + try { + if (draft?.id) { + + // Atualiza o rascunho existente + await mecredApi + .post(`api/resource/update`, newDraft, {headers: authHeaders()}); + } else { + // Cria um novo rascunho + const response = await mecredApi.post("api/resource/create", newDraft, {headers: authHeaders()}); + const createdDraft = response.data; + setDraft(createdDraft); // atualiza com o draft com ID gerado + } + + console.log("Draft salvo com sucesso PAGINA 1:", newDraft); setStep((curr) => curr + 1); + } catch (error) { + console.error("Erro ao salvar o draft PAGINA 2:", error); } + }; + - } return ( <> <form className="mb-10" onSubmit={handleSubmit} onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault() }} > - <Paper - className="rounded-lg p-10 max-xl:p-5 max-xl:mx-5 " + <Paper + className="rounded-lg p-10 max-xl:p-5 max-xl:mx-5 " elevation={1} sx={{ backgroundColor: 'var(--white-HC-dark)', @@ -187,10 +210,11 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp <h1 className="text-darkGray-HC-white font-light text-2xl text-center"> Os campos marcados com * são obrigatórios </h1> - <FieldLabel - name="Título do Recurso*" + <FieldLabel + name="Título do Recurso*" error={(!title && filled)} /> + <CustomTextField multiline fullWidth @@ -204,9 +228,9 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp className="mb-5" sx={{ "& .MuiOutlinedInput-input::placeholder": { - color: "var(--darkGray-HC-white)", - opacity: 0.5, - }, + color: "var(--darkGray-HC-white)", + opacity: 0.5, + }, }} /> <FieldLabel name="Descrição geral do Recurso*" error={(!description && filled)} /> @@ -223,7 +247,8 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp error={(description.length > 1500) || (!description && filled)} className="mb-5" /> - <FieldLabel + + {/* <FieldLabel name="Palavras-chave Relacionadas ao Conteúdo do seu Recurso*" description="(Use Enter ou vírgula para confirmar a palavra-chave)" error={(!tags.length && filled)} @@ -240,7 +265,8 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp </div>} className="mb-5" /> - <TagInput tags={tags} handleTagDelete={handleTagDelete} /> + <TagInput tags={tags} handleTagDelete={handleTagDelete} /> */} + <AuthorSelection selectedValue={authorType} handleRadioChange={handleRadioChange} @@ -252,30 +278,40 @@ export default function InfoForm({ userData, setStep, draft, setDraft, authorTyp selected={objectTypes} handleSelection={handleObjectsTypeChange} /> - <FieldLabel name="Idiomas do Recurso*" error={!languages.length && filled} /> + <FieldLabel name="Idiomas do Recurso*" error={!languageIds.length && filled} /> <FormGroup className="flex flex-wrap mb-10" row> - {languagesAvailable?.map((language, index) => ( + {languagesAvailable.map((language) => ( <FormControlLabel - onChange={handleLanguagesChange} - key={index} - control={<CustomCheckbox checked={languages.includes(language.name)} />} - value={JSON.stringify(language)} + key={language.id} + control={ + <Checkbox + checked={languageIds.includes(language.id)} + onChange={handleLanguagesChange} + value={language.id} + /> + } label={<span className="text-darkGray-HC-white">{language.name}</span>} /> ))} </FormGroup> + <FieldLabel name="Nível de ensino" description="(Selecione uma ou mais opções)*" error={!scholarityLevelsId.length && filled} /> + <FormGroup className="flex flex-wrap mt-5 mb-10"> - {scholarityLevelsAvailable.map((sl, index) => ( + {scholarityLevelsAvailable.map((sl) => ( <FormControlLabel - onChange={handleScholarityChange} - key={index} - control={<CustomCheckbox checked={scholarityLevelsName.includes(sl.name)} />} - value={JSON.stringify(sl)} + key={sl.id} + control={ + <Checkbox + checked={scholarityLevelsId.includes(sl.id)} + onChange={handleScholarityChange} + value={sl.id} + /> + } label={<span className="text-darkGray-HC-white">{sl.name}</span>} /> ))} diff --git a/src/app/publicar/components/ObjectTypeSelection.js b/src/app/publicar/components/ObjectTypeSelection.js index 5b149e353d43d4254861eac6d8358cd54881c0d6..e77a34cb9ddcab1e9d4f3c16bed32a4a5ee11738 100644 --- a/src/app/publicar/components/ObjectTypeSelection.js +++ b/src/app/publicar/components/ObjectTypeSelection.js @@ -8,7 +8,6 @@ export default function ObjectTypeSelection({ selected, handleSelection, error } <FieldLabel name="Tipo de Recurso*" error={error} /> <div className="flex flex-wrap justify-center"> {objectTypes.map((subject, index) => { - return ( <SelectOption key={index} diff --git a/src/app/publicar/components/RevisionForm.js b/src/app/publicar/components/RevisionForm.js index 85c13fab15131780861a6103651647f22197ef5c..8c238deb60c3fcc6e97bcb73281b50d507a893bf 100644 --- a/src/app/publicar/components/RevisionForm.js +++ b/src/app/publicar/components/RevisionForm.js @@ -1,7 +1,6 @@ "use client" import mecredApi from "@/axiosConfig" import { Button, Paper } from "@mui/material" -import { getStoredValue } from "@/app/handlers/localStorageHandler"; import { useEffect, useState } from "react"; import Tags from "@/app/components/tags"; import PublisherInfo from "@/app/components/publisherInfo"; @@ -10,113 +9,66 @@ import ResourcePreview from "@/app/recurso/[id]/components/resourcePreview"; import ModalSuccess from "./ModalSuccess"; import { useRouter } from "next/navigation"; import CancelSubmitModal from "./CancelSubmitModal"; +import { authHeaders } from "@/app/handlers/loginHandler"; -function headerConfig() { - return { - headers: { - "access-token": getStoredValue("access_token"), - "token-type": "Bearer", - client: getStoredValue("client"), - uid: getStoredValue("uid"), - Expires: 0, - } - }; -} -export default function RevisionForm({ setStep, draft, setDraft }) { +export default function RevisionForm({ userData, setStep, draft, setDraft }) { const [permission, setPermission] = useState(false) const [openModal, setOpenModal] = useState(false) + const [preview, setPreview] = useState(draft); + const [openModalCancel, setOpenModalCancel] = useState(false) - let userData = JSON.parse(getStoredValue("user_data")) const router = useRouter(); - /// Ira ser enviado ao backend - let data = { - attachmentID: draft.attachmentID, - file: draft.file, - name: draft.name, - description: draft.description, - tags: draft.tags.map(tag => { return { name: tag } }), - author: draft.author, - object_type_id: draft.object_type_f[0].id, - language_ids: draft.language_ids, - language_names: draft.language, - educational_stages: draft.educational_stages, - subjects: draft.subjects.map(subj => { return subj.id }), - license_id: draft.license_id, - link: draft.link, - } - - /// Irá ser enviado ao componente de visualizacao para preview - let preview = { - ...draft, - default_mime_type: draft.attachments[0]?.mime_type, - object_type: draft.object_type_f[0]?.name, - language: draft.language.map(lang => { return { name: lang } }), - tags: draft.tags.map(tag => { return { name: tag } }), - subjects: draft.subjects.map(subject => { return { name: subject.name } }), - educational_stages: draft.educational_stages_name.map(educa => { return { name: educa } }) - } - - useEffect(() => { - mecredApi.get("/learning_objects/" + draft.id, headerConfig()) - .then((response) => { - setDraft(curr => { - return { ...curr, attachments: response.data.attachments } - }) - }); - - }, [draft.id, setDraft]) - useEffect(() => { for (let i = 0; i < userData.roles.length; i++) { - if (userData.roles[i].name === "partner" || userData.roles[i].name === "admin") { + if (userData.roles[i] === "admin" || userData.roles[i] === "contributor") { setPermission(true) return } } - }, [userData.roles]) + const fetchPreview = async () => { + await mecredApi + .get(`public/resource/${draft.id}`) + .then((response) => setPreview(response.data)); + console.log("resposta", preview) + }; + fetchPreview(); + }, []) - const submitHomologation = () => { - mecredApi - .put(`/learning_objects/${draft.id}`, data, headerConfig()) - .then( - () => { - let payload = { - "submission": { - "learning_object_id": draft.id - } - } + const submitHomologation = () => { - return ( - mecredApi - .post("/submissions", payload, headerConfig()) - ) - } - ) + const previewData = { + ...draft, + state: 'submitted' + } + + mecredApi + .post('api/resource/update', previewData, { headers: authHeaders() }) .then( setOpenModal(true) ) } + //publica direto const fetchSubmit = () => { + const previewData = { + ...draft, + state: 'accepted' + } + mecredApi - .put(`/learning_objects/${draft.id}`, data, headerConfig()) - .then( - () => - mecredApi - .post(`/learning_objects/${draft.id}/publish`, {}, headerConfig()) - ) + .post('api/resource/update', previewData, { headers: authHeaders() }) .then( setOpenModal(true) ) @@ -129,21 +81,21 @@ export default function RevisionForm({ setStep, draft, setDraft }) { openModal={openModal} onClose={() => { setOpenModal(false); - router.push("/busca?page=LearningObject"); + router.push("/sobre"); }} - permission={permission} + permission={permission} idResource={draft.id} - /> + /> <form> - <Paper + <Paper className="rounded-lg p-10 max-xl:mx-5 mx-5" elevation={1} sx={{ backgroundColor: 'var(--white-HC-dark)', border: '2px solid var(--ice-HC-white)', }} - > + > <div className="flex flex-col text-center py-6"> <p className="text-2xl text-darkGray-HC-white pb-6"> Quase lá, agora só falta revisar e submeter! @@ -165,15 +117,17 @@ export default function RevisionForm({ setStep, draft, setDraft }) { </div> <div className="text-darkGray-HC-white mt-5 text-2xl font-bold"> {/* Título */} - <h1>{draft.name}</h1> + <h1>{preview.name}</h1> </div> + <div className="text-darkGray-HC-white mt-3 text-sm font-bold"> {/* tags */} - <Tags tags={preview.tags} /> + {/* <Tags tags={preview.tags} /> */} </div> + <div className=""> {/* Publicador */} - <PublisherInfo publisher={draft?.publisher} disabledButton={true} /> + {/* <PublisherInfo publisher={draft?.publisher} disabledButton={true} /> */} </div> <div className=""> {/* Informações */} diff --git a/src/app/publicar/components/SelectOption.js b/src/app/publicar/components/SelectOption.js index 0ed92d5baf0594b0f1eb9df31c9e831dd71cbd5d..f11182d80f153db59b2d14e6cd4a6a10559492f7 100644 --- a/src/app/publicar/components/SelectOption.js +++ b/src/app/publicar/components/SelectOption.js @@ -1,32 +1,24 @@ import Image from "next/image"; export default function SelectOption({ selected, handleSelection, subject }) { - return ( <div - onClick={() => handleSelection(subject)} + onClick={() => handleSelection(subject.id)} className={`group bg-ice-HC-dark hover:bg-lightGray-HC-white hover:cursor-pointer h-24 w-24 max-lg:h-20 max-lg:w-20 border-2 flex flex-col m-2 items-center p-2 text-center justify-center rounded-lg ${ - selected.includes(subject) && "border-2 border-turquoise-HC-white bg-lightGray-HC-white " + selected && Array.isArray(selected) && selected?.includes(subject.id) && "border-2 border-turquoise-HC-white bg-lightGray-HC-white " }`} > - {/* <Image - alt={subject.name} - height={0} - width={0} - className="w-12 h-12 max-lg:w-8 max-lg:h-8 fill-current text-pink" - src={subject.image} - /> */} <div alt={subject.name} className={`w-12 h-12 max-lg:w-8 max-lg:h-8 fill-current - ${selected.includes(subject) ? "text-turquoise-HC-dark" : "text-turquoise-HC-white group-hover:text-turquoise-HC-dark"} + ${selected && Array.isArray(selected) && selected?.includes(subject.id) ? "text-turquoise-HC-dark" : "text-turquoise-HC-white group-hover:text-turquoise-HC-dark"} `} dangerouslySetInnerHTML={{ __html: subject.image }} ></div> <span className={`text-xs max-lg:text-xs - ${selected.includes(subject) ? "text-turquoise-HC-dark-underline" : "text-turquoise-HC-white-underline group-hover:text-turquoise-HC-dark-underline"} + ${selected && Array.isArray(selected) && selected?.includes(subject) ? "text-turquoise-HC-dark-underline" : "text-turquoise-HC-white-underline group-hover:text-turquoise-HC-dark-underline"} `}>{subject.name}</span> </div> ); diff --git a/src/app/publicar/components/SubmitThumbnail.js b/src/app/publicar/components/SubmitThumbnail.js index 91368145e0e08474bb3cf89fb07cc0f457b9023e..1f7ff7fa854bbf7b1c155e808fb4c5669f78da86 100644 --- a/src/app/publicar/components/SubmitThumbnail.js +++ b/src/app/publicar/components/SubmitThumbnail.js @@ -14,22 +14,24 @@ export default function SubmitThumbnail({ error, setThumb, thumbURL, setThumbURL const handleThumbnail = (e) => { - if (!imageType(e.target.files[0]?.type)) { - e.target.files = null - setOpenType(true) - return + const file = e.target.files[0]; + + if (!imageType(file?.type)) { + e.target.value = ""; + setOpenType(true); + return; } - - if (e.target.files[0].size > 4e6) { - e.target.files = null - setOpen(true) - return + + if (file.size > 4 * 1024 * 1024) { + e.target.value = ""; + setOpen(true); + return; } - - e.preventDefault() - setThumb(e.target.files[0]); - setThumbURL(URL.createObjectURL(e.target.files[0])); - }; + + e.preventDefault(); + setThumb(file); + setThumbURL(URL.createObjectURL(file)); + }; return ( diff --git a/src/app/publicar/components/UploadFile.js b/src/app/publicar/components/UploadFile.js index a87f91db5364346fe346edd39f7eccbcff86c20f..b8e88c70e0836c858742f0a2d932ac97063c614c 100644 --- a/src/app/publicar/components/UploadFile.js +++ b/src/app/publicar/components/UploadFile.js @@ -1,75 +1,43 @@ "use client"; -import { Button, LinearProgress } from "@mui/material"; +import { Button } from "@mui/material"; import mecredApi from "@/axiosConfig"; -import { getStoredValue } from "@/app/handlers/localStorageHandler"; import FileUploadIcon from '@mui/icons-material/FileUpload'; import { useState } from "react"; import { imageType, videoType, textType, audioType } from "@/app/components/FileTypeUtilities"; import ErrorType from "./ErrorType"; import ErrorSize from "./ErrorSize"; +import { authHeaders } from "@/app/handlers/loginHandler"; -//const chunkSize = 262144; -const chunkSize = 1024 * 1024; - -function headerConfig() { - return { - headers: { - "access-token": getStoredValue("access_token"), - "token-type": "Bearer", - client: getStoredValue("client"), - uid: getStoredValue("uid"), - Expires: 0, - } - }; -} -export default function UploadFile({ setUploading, setProgress, draft, file, setFile, attachmentId, setAttachmentId }) { +export default function UploadFile({ setUploading, uploading, setProgress, draft, file, setFile }) { const [open, setOpen] = useState(false); const [openType, setOpenType] = useState(false); const fileUpload = async (file) => { setUploading("sending"); - let numChunksToSend = Math.ceil(file.size / chunkSize); - let start = 0; - let response = null; - - for (let i = 0; i < numChunksToSend; i++) { - let fileSlice = file.slice(start, Math.min(start + chunkSize, file.size)); - - let formData = new FormData(); - formData.append('_chunkFilename', file.name); - formData.append('_chunkIdentifier', draft.id + '-' + file.name); - formData.append('_totalChunks', numChunksToSend); - formData.append('_chunkSize', chunkSize); - formData.append('_currentChunkSize', fileSlice.size); - formData.append('_chunkNumber', i); - formData.append('_totalSize', file.size); - formData.append('file', fileSlice); - formData.append('cancel', false); - - response = await mecredApi.post("/learning_objects/" + draft.id + "/chunk", formData, headerConfig()) - .catch((error) => console.error(error)); - - // Atualiza o progresso com base nos chunks enviados - setProgress(Math.round(((i + 1) / numChunksToSend) * 100)); - start += chunkSize; - } + let formData = new FormData(); + formData.set('file', file); + formData.set('id_resource', String(draft.id)); + formData.set('content_type', file.type); // tipo MIME do arquivo (image/png, image/jpeg etc) + + + await mecredApi + .post("api/s3/upload/resource", formData, { headers: authHeaders() }) + .catch((error) => console.error(error)); - setAttachmentId(response.data.id); setUploading("file"); } const handleDelete = async () => { - let url = `/learning_objects/${draft.id}/attachment/${attachmentId}`; + let url = `api/s3/delete/resource/${draft.id}`; await mecredApi - .delete(url, headerConfig()) + .delete(url, { headers: authHeaders() }) .then(() => { - setAttachmentId(null); setUploading("nothing"); setFile(null); - setProgress(0); // Reset progress + setProgress(0); //reseta a barra de progresso }) .catch((error) => console.error(error)); } @@ -109,7 +77,7 @@ export default function UploadFile({ setUploading, setProgress, draft, file, set <FileUploadIcon fontSize="large" className="text-darkGray-HC-dark" /> </div> - {!attachmentId ? ( + {uploading === "nothing" ? ( <> <h1 className="text-center text-2xl text-darkGray-HC-white"> Envie o arquivo do recurso diff --git a/src/app/publicar/components/UploadForm.js b/src/app/publicar/components/UploadForm.js index a766456ec61429d00d5d630d0fd58b817438975d..07d6d80ca227731f86011ac3b1d7dfcd8df76fad 100644 --- a/src/app/publicar/components/UploadForm.js +++ b/src/app/publicar/components/UploadForm.js @@ -9,29 +9,15 @@ import UploadFile from "./UploadFile"; import { useState } from "react"; import SubmitThumbnail from "./SubmitThumbnail"; import mecredApi from "@/axiosConfig"; -import { getStoredValue } from "@/app/handlers/localStorageHandler"; import ModalIncompleteField from "./ModalIncompleteField"; import CancelSubmitModal from "./CancelSubmitModal"; import HelpIcon from '@mui/icons-material/Help'; import Tooltip, { tooltipClasses } from '@mui/material/Tooltip'; import { styled } from '@mui/material/styles'; import { LinearProgress } from "@mui/material"; +import { authHeaders } from "@/app/handlers/loginHandler"; - - -function headerConfig() { - return { - headers: { - "access-token": getStoredValue("access_token"), - "token-type": "Bearer", - client: getStoredValue("client"), - uid: getStoredValue("uid"), - Expires: 0, - } - }; -} - const CustomWidthTooltip = styled(({ className, ...props }) => ( <Tooltip {...props} classes={{ popper: className }} /> ))({ @@ -55,20 +41,22 @@ const longText = () => { }; -export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, setStep, draft, setDraft, file, setFile, attachmentId, setAttachmentId }) { +export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, setStep, draft, setDraft, file, setFile }) { const [uploading, setUploading] = useState(draft.link ? "link" : file ? "file" : "nothing") const [filled, setFilled] = useState(false) const [openModal, setOpenModal] = useState(false) const [openModalCancel, setOpenModalCancel] = useState(false) const [progress, setProgress] = useState(0); // Estado para a barra de progresso - const submitThumb = async () => { let finalThumb = new FormData() - finalThumb.set('learning_object[thumbnail]', thumb); + finalThumb.set('file', thumb); + finalThumb.set('id_resource', String(draft.id)); + finalThumb.set('content_type', thumb.type); // tipo MIME do arquivo (image/png, image/jpeg etc) + await mecredApi - .put(`/learning_objects/${draft.id}`, finalThumb, headerConfig()) + .post(`api/s3/upload/thumbnail/resource`, finalThumb, { headers: authHeaders() }) .catch((error) => { console.error(error) }) } @@ -88,29 +76,29 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set } else { submitThumb(); - setDraft((curr) => ({ - ...curr, - attachmentID: (JSON.stringify(attachmentId)), - file: file?.name, - })) + + mecredApi + .post('api/resource/update', draft, { headers: authHeaders() }) + .then((response) => { setDraft(response.data) }) + + setStep(curr => curr + 1); } } - function linkOrFileUpload(uploading, setUploading, attachmentId, file, link) { + function linkOrFileUpload(uploading, setUploading, file, link) { switch (uploading) { case "nothing": return ( - draft.object_type_f[0].name !== "Website Externo" ? + draft.object_type_id !== 13 ? <> <div className="pb-10"> - <UploadFile setUploading={setUploading} setProgress={setProgress} draft={draft} setDraft={setDraft} file={file} setFile={setFile} attachmentId={attachmentId} setAttachmentId={setAttachmentId} /> + <UploadFile setUploading={setUploading} uploading={uploading} setProgress={setProgress} draft={draft} setDraft={setDraft} file={file} setFile={setFile} /> </div> </> - : <> <SubmitLink setUploading={setUploading} setDraft={setDraft} draft={draft} /> @@ -126,7 +114,7 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set ) case "file": return ( - <UploadFile setUploading={setUploading} setProgress={setProgress} draft={draft} setDraft={setDraft} file={file} setFile={setFile} attachmentId={attachmentId} setAttachmentId={setAttachmentId} /> + <UploadFile setUploading={setUploading} setProgress={setProgress} draft={draft} setDraft={setDraft} file={file} setFile={setFile} /> ) case "link": return ( @@ -138,8 +126,8 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set return ( <> <form className="mb-10" onKeyDown={(e) => { if (e.key === "Enter") e.preventDefault() }}> - <Paper - className="rounded-lg px-10 py-1 max-xl:mx-5" + <Paper + className="rounded-lg px-10 py-1 max-xl:mx-5" elevation={1} sx={{ backgroundColor: 'var(--white-HC-dark)', @@ -147,13 +135,13 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set }} > <div className="flex w-full justify-between"> - <FieldLabel - name="Enviar Recurso*" - description={ - draft.object_type_f[0].name !== "Website Externo" ? "Envie um arquivo" : "Envie um link externo" - } - error={(filled && !draft.link) && (filled && !file)} - /> + <FieldLabel + name="Enviar Recurso*" + description={ + draft.object_type_id !== 13 ? "Envie um arquivo" : "Envie um link externo" + } + error={(filled && !draft.link) && (filled && !file)} + /> <div className="mt-10 pl-8"> <CustomWidthTooltip title={longText()} > <HelpIcon fontSize="large" className="text-turquoise-HC-white " /> @@ -162,14 +150,14 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set </div> </div> - {linkOrFileUpload(uploading, setUploading, attachmentId, file, draft.link)} + {linkOrFileUpload(uploading, setUploading, file, draft.link)} <Divider /> - <SubmitThumbnail error={filled && !thumbURL} setThumb={setThumb} thumbURL={thumbURL} setThumbURL={setThumbURL} draft={draft} setDraft={setDraft} /> + <SubmitThumbnail error={filled && !thumbURL} setThumb={setThumb} thumbURL={thumbURL} setThumbURL={setThumbURL} draft={draft} setDraft={setDraft} /> </Paper> <h1 className="text-darkGray-HC-white font-light text-base pt-1 text-end max-xl:pr-6"> - + </h1> <div className="flex w-full justify-end p-3"> <Button @@ -184,11 +172,6 @@ export default function UploadForm({ thumbURL, thumb, setThumb, setThumbURL, set variant="text" className="normal-case w-36 text-base text-darkGray-HC-white-underline font-bold mr-2 outline outline-1 outline-ice-HC-white bg-ice-HC-dark hover:bg-lightGray-HC-white hover:text-darkGray-HC-dark-underline" onClick={() => { - setDraft((curr) => ({ - ...curr, - attachmentID: (JSON.stringify(attachmentId)), - file: file?.name, - })) setStep(curr => curr - 1) } } diff --git a/src/app/publicar/page.js b/src/app/publicar/page.js index 84ca7547d12a97baca90c1ae818143c32f8efcc6..4b9cbc08567d86f1b82a42f48f6040d6aae5489c 100644 --- a/src/app/publicar/page.js +++ b/src/app/publicar/page.js @@ -1,15 +1,12 @@ "use client"; import Overlay from "../components/Overlay"; -import { useLoginBarrier } from "../handlers/loginHandler"; +import { useLoggedIn } from "../handlers/loginHandler"; import Form from "./components/Form"; -import { useEffect } from "react"; export default function Submit() { - const loginBarrier = useLoginBarrier(); - - useEffect(() => { - loginBarrier() - }, [loginBarrier]) + const loggedIn = useLoggedIn(); + + if(!loggedIn) return; return ( <Overlay> diff --git a/src/app/recurso/[id]/components/actionButtons.js b/src/app/recurso/[id]/components/actionButtons.js index 384f6cfab44c958c6c7b2c35ed07e37328de7597..de4769bf2d29dda676f84c02df3b17c5bc0da9d9 100644 --- a/src/app/recurso/[id]/components/actionButtons.js +++ b/src/app/recurso/[id]/components/actionButtons.js @@ -9,7 +9,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import FlagOutlinedIcon from "@mui/icons-material/FlagOutlined"; import FlagIcon from "@mui/icons-material/Flag"; import mecredApi, { mecredURL } from "@/axiosConfig"; -import { isLoggedIn, useLoginBarrier } from "@/app/handlers/loginHandler"; +import { authHeaders, isLoggedIn, useLoginBarrier } from "@/app/handlers/loginHandler"; import { getStoredValue } from "@/app/handlers/localStorageHandler"; import { useState, useEffect, useLayoutEffect } from "react"; import ShareModal from "../../../components/ShareModal"; @@ -40,9 +40,6 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state const [submitted, setSubmitted] = useState(state) const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); - const pathname = usePathname(); useEffect(() => { if (!isLoggedIn()) return; @@ -56,42 +53,43 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state }, [learningObject.liked, userData]); useEffect(() => { - if(!isLoggedIn() || !learningObject?.id) return; + if (!isLoggedIn() || !learningObject?.id) return; setReported(learningObject.complained); }, [learningObject]) + let buttonInfo = [ - // Botão de Acessar PDF - learningObject.default_mime_type !== "application/pdf" ? null : { - name: "Acessar", - icon: <OpenInNewOutlinedIcon />, - action: () => { - if (typeof window !== "undefined") { - const uri = - mecredURL + "inline/" + learningObject.id; - window.open(uri); - } - }, - extraProps: {}, + // Botão de Acessar PDF + learningObject.contentType !== "application/pdf" ? null : { + name: "Acessar", + icon: <OpenInNewOutlinedIcon />, + action: () => { + if (typeof window !== "undefined") { + const uri = + mecredURL + "inline/" + learningObject.id; + window.open(uri); + } }, - // Botão de Acessar ou Baixar - { - name: learningObject.link ? "Acessar" : "Baixar", - icon: learningObject.link ? ( - <OpenInNewOutlinedIcon /> - ) : ( - <DownloadOutlinedIcon /> - ), - action: () => { - if (typeof window !== "undefined") { - window.open( - mecredApi.getUri() + - `/learning_objects/${learningObject.id}/download` - ); - } - }, - extraProps: {}, + extraProps: {}, + }, + // Botão de Acessar ou Baixar + { + name: learningObject.link ? "Acessar" : "Baixar", + icon: learningObject.link ? ( + <OpenInNewOutlinedIcon /> + ) : ( + <DownloadOutlinedIcon /> + ), + action: () => { + if (typeof window !== "undefined") { + window.open( + mecredApi.getUri() + + `public/resource/download/${learningObject.id}` + ); + } }, + extraProps: {}, + }, // Botão de curtir { name: liked ? ( @@ -107,23 +105,17 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state } mecredApi.put( - `learning_objects/${learningObject.id}/like`, + `api/resourceLikes/like/${learningObject.id}`, {}, { - headers: { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, - }, + headers: authHeaders(), } ); setLiked(!liked); }, extraProps: {}, }, - + // Botão de Colecionar { name: "Colecionar", icon: <BookmarkBorderOutlinedIcon />, action: () => { @@ -159,25 +151,25 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state }, extraProps: { disabled: reported }, }, - userData.id !== learningObject.publisher.id ? null : - { - name: "Deletar", - icon: <DeleteOutlinedIcon />, - action: () => { - if (!isLoggedIn()) { - setNeedLoginOpen(true); - return; - } + userData.id !== learningObject.user_id ? null : + { + name: "Deletar", + icon: <DeleteOutlinedIcon />, + action: () => { + if (!isLoggedIn()) { + setNeedLoginOpen(true); + return; + } - setDeleteOpen(true); - }, - extraProps: {}, - } + setDeleteOpen(true); + }, + extraProps: {}, + } ]; let buttonKey = 0; let genButton = (info) => ( - !info ? null : <Button + !info ? null : <Button id="conteudo" key={buttonKey++} className="group first:bg-turquoise-HC-white first:hover:text-darkGray-HC-white first:text-darkGray-HC-dark first:hover:bg-darkTurquoise-HC-dark text-darkGray-HC-white group outline outline-1 outline-ice-HC-white group/button shadow-none flex justify-start rounded-lg p-1 m-1 normal-case text-sm min-w-min font-bold bg-white-HC-dark hover:bg-lightGray-HC-white text-darkGray-HC-white-underline hover:text-darkGray-HC-dark focus:bg-ice-HC-dark" @@ -188,7 +180,7 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state <div className="p-1 text-darkGray-HC-white group-hover:text-darkGray-HC-dark-underline group-first:group-hover:text-white-HC-underline group-first:text-white-HC-dark-underline">{info.name}</div> </Button> ); - + return ( <> @@ -204,13 +196,13 @@ export default function ActionButtons({ learningObject, setNeedLoginOpen, state {/* Verifica se o usuário está logado antes de abrir o modal de colecionar */} {userData && <CollectModal open={collectOpen} onClose={() => setCollectOpen(false)} idLogin={userData["id"]} resourceId={learningObject.id} />} {!submitted ? - <div className="flex justify-start py-4 max-sm:no-scrollbar max-sm:overflow-x-auto animate-scrollHint"> + <div className="flex justify-start py-4 max-sm:no-scrollbar overflow-x-auto animate-scrollHint"> {buttonInfo.map(genButton)} </div> : <div className="flex justify-start py-4 max-sm:no-scrollbar max-sm:overflow-x-auto animate-scrollHint text-red-800"> {genButton(buttonInfo[1] || buttonInfo[2])} - </div> + </div> } </> ); diff --git a/src/app/recurso/[id]/components/collectionInfo.js b/src/app/recurso/[id]/components/collectionInfo.js index 62d25ab6000c15e355bda8f23e446f57f04bc175..92a64f8f9a93208397d6df56b2f22a7235d0a41a 100644 --- a/src/app/recurso/[id]/components/collectionInfo.js +++ b/src/app/recurso/[id]/components/collectionInfo.js @@ -20,21 +20,16 @@ export default function CollectionInfo({ resourceId, collectionId }) { if (isLoggedIn()) { headers = { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, + Authorization:`bearer ${token}` }; } - const response = await mecredApi.get(`collections/${collectionId}`, { + const response = await mecredApi.get(`public/collection/${collectionId}`, { headers: headers, }); - setCollection(response.data); }; - + fetchData(); }, [collectionId, client, token, uid]); diff --git a/src/app/recurso/[id]/components/comments.js b/src/app/recurso/[id]/components/comments.js index ace9f2cba6be88409987707fc7f6762734bd5a25..62c4a57c05dd0cea7ea5215c84c18891002d3a18 100644 --- a/src/app/recurso/[id]/components/comments.js +++ b/src/app/recurso/[id]/components/comments.js @@ -1,62 +1,43 @@ -import { Paper, Modal, Button, Accordion, AccordionSummary, Typography, AccordionDetails } from "@mui/material"; +import { Paper, Accordion, AccordionSummary, AccordionDetails } from "@mui/material"; import { useEffect, useState } from "react"; import mecredApi from "@/axiosConfig"; -import { getStoredValue } from "@/app/handlers/localStorageHandler"; import PrintComments from "./printComments"; import CreateComments from "./createComments"; -import { isLoggedIn } from "@/app/handlers/loginHandler"; -import { setConfig } from "next/config"; +import {authHeaders, useLoggedIn } from "@/app/handlers/loginHandler"; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import { userData } from "@/app/handlers/loginHandler"; + export default function Comments({ learningObjectId }) { const [comments, setComments] = useState(null) const [newComment, setNewComment] = useState(""); - - - const token = getStoredValue("access_token") - const client = getStoredValue("client") - const uid = getStoredValue("uid") - - const [userData, setUserData] = useState({}); + const loggedIn = useLoggedIn(); + + + const [userDatas, setUserDatas] = useState({}); const [logged, setLogged] = useState(false); useEffect(() => { - if (!isLoggedIn()) return; - const data = JSON.parse(getStoredValue("user_data")); - setUserData(data); + if (!loggedIn) return; + setUserDatas(userData()); setLogged(true); - }, [token]) + }, [loggedIn, userDatas]) const handleSubmitComment = async () => { - if (!logged) return + if (!loggedIn) return if (newComment === "") return - const url = `/learning_objects/${learningObjectId}/reviews` + const url = `api/comments/create` let payload = { - review: { - description: newComment, - review_ratings_attributes: [ - { - rating_id: 1, - value: 5, - }, - ], - }, + resource_id: learningObjectId, + text: newComment, }; try { await mecredApi - .post(url, payload, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - },) + .post(url, payload, {headers: authHeaders()},) .then((response) => setComments(old => [...old, response.data])) } catch (error) { console.error(error) @@ -66,39 +47,26 @@ export default function Comments({ learningObjectId }) { setNewComment("") } - // `/learning_objects/${props.recursoId}/reviews?page=${currPageReviews}` useEffect(() => { - - - const url = `/learning_objects/${learningObjectId}/reviews?page=0` - + const url = `/public/comments/findCommentsByResource/${learningObjectId}` const getComments = async (url) => { await mecredApi - .get(url, { - headers: { - 'access-token': token, - 'token-type': 'Bearer', - 'client': client, - 'uid': uid, - 'Expires': 0 - } - }) + .get(url) .then(({ data }) => { setComments(data) } ) } - getComments(url) - }, [token, learningObjectId, client, uid]) + }, [learningObjectId]) return ( <div> {comments?.length === 0 ? <div className="sm:hidden h-auto pt-4 my-4 bg-white-HC-dark flex flex-col items-center gap-4"> <p className="text-darkGray-HC-white text-xl">Ainda não possui comentários...</p> - <CreateComments user={userData} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> + <CreateComments user={userDatas} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> </div> : <Accordion className="sm:hidden bg-white-HC-dark "> @@ -122,7 +90,7 @@ export default function Comments({ learningObjectId }) { return <PrintComments key={index} comment={message} - userData={userData} + userData={userDatas} learningObjectId={learningObjectId} comments={comments} setComments={setComments} @@ -133,7 +101,7 @@ export default function Comments({ learningObjectId }) { </div> <hr className="max-sm:my-2" /> <div className="sm:hidden h-24 pt-4"> - <CreateComments user={userData} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> + <CreateComments user={userDatas} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> </div> </AccordionDetails> </Accordion> @@ -145,7 +113,7 @@ export default function Comments({ learningObjectId }) { {comments?.length} {comments?.length === 1 ? "comentário" : "comentários"} </div> <div> - <CreateComments user={userData} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> + <CreateComments user={userDatas} setComments={setComments} comments={comments} logged={logged} learningObjectId={learningObjectId} newComment={newComment} setNewComment={setNewComment} handleSubmitComment={handleSubmitComment} /> </div> <div> <div className="text-darkGray-HC-white "> @@ -153,7 +121,7 @@ export default function Comments({ learningObjectId }) { return <PrintComments key={index} comment={message} - userData={userData} + userData={userDatas} learningObjectId={learningObjectId} comments={comments} setComments={setComments} diff --git a/src/app/recurso/[id]/components/complaintsModal.js b/src/app/recurso/[id]/components/complaintsModal.js index b76bef237d89c3a69e4a70d5d089428c5069daa4..7ae7d9da16c925229c361b919d39fc0e55ba6733 100644 --- a/src/app/recurso/[id]/components/complaintsModal.js +++ b/src/app/recurso/[id]/components/complaintsModal.js @@ -3,7 +3,7 @@ import { getStoredValue } from "@/app/handlers/localStorageHandler"; import mecredApi from "@/axiosConfig"; import { Alert, FormControlLabel, Modal, Radio, RadioGroup, TextField } from "@mui/material"; import { useEffect, useState } from "react"; -import { isLoggedIn } from "@/app/handlers/loginHandler"; +import { isLoggedIn, userData } from "@/app/handlers/loginHandler"; import CloseIcon from '@mui/icons-material/Close'; export default function ComplaintModal({ open, onClose, name, id, setSubmitted }) { @@ -27,8 +27,9 @@ export default function ComplaintModal({ open, onClose, name, id, setSubmitted } useEffect(() => { if (isLoggedIn()) { - let data = JSON.parse(getStoredValue("user_data")); - setMyId(data["id"]); + let data = userData(); + console.log(data, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa") + setMyId(data.id); } }, []); diff --git a/src/app/recurso/[id]/components/editComments.js b/src/app/recurso/[id]/components/editComments.js index 132acff8aec431342e1d995be7211a9fbc78a5f1..0164520c80f0514a87f78df57a90957c2448c36d 100644 --- a/src/app/recurso/[id]/components/editComments.js +++ b/src/app/recurso/[id]/components/editComments.js @@ -49,7 +49,7 @@ return ( <div className="flex flex-col gap-1 w-full "> <div className="bg-ice-HC-dark rounded-lg p-4 outline outline-1 outline-ice-HC-white "> <div className="text-lg pb-2 text-darkGray-HC-white"> - {comment.user.name} + {comment.author_name} </div> <div className="flex flex-col mt-2 gap-1 w-full justify-center "> diff --git a/src/app/recurso/[id]/components/metrics.js b/src/app/recurso/[id]/components/metrics.js index 6da9c36d632adcb6bf9b050e744d224f1c215bb7..1641c6e782fda08dc85dd97874c5bccb3e790013 100644 --- a/src/app/recurso/[id]/components/metrics.js +++ b/src/app/recurso/[id]/components/metrics.js @@ -26,6 +26,10 @@ const genMetric = ({ icon, name, data }) => { } export default function Specifications({ learningObject }) { + console.log(learningObject) + console.log(learningObject.author) + console.log(learningObject.publisher?.name) + console.log(learningObject.author) const metricInfo = [ { icon: <IconViews className="text-2xl invertLogo-HC-white" />, name: "Visualizações", data: learningObject.views_count }, { icon: <FavoriteOutlinedIcon />, name: "Curtidas", data: learningObject.likes_count }, diff --git a/src/app/recurso/[id]/components/printComments.js b/src/app/recurso/[id]/components/printComments.js index e3059f2a7cdee314b333116af40a870b16806ff1..81e1df7934be56d8a071c5c7801a575aa6e2d9a2 100644 --- a/src/app/recurso/[id]/components/printComments.js +++ b/src/app/recurso/[id]/components/printComments.js @@ -4,7 +4,7 @@ import DeleteCommentModal from "./deleteCommentModal"; import Link from "next/link"; import EditComment from "./editComments"; -import { mecredURL } from "@/axiosConfig"; +import { s3URL } from "@/axiosConfig"; @@ -25,51 +25,52 @@ function getRandomBg(id) { } - - - export default function PrintComments({ comment, userData, learningObjectId, comments, setComments }) { const [openDeleteModal, setOpenDeleteModal] = useState(false) const [edit, setEdit] = useState(false) const timeFunction = (updated_time) => { - let data = new Date(updated_time) - let dataAtual = new Date(); - let time = dataAtual.getTime() - data.getTime(); - let dia = Math.floor(time / (1000 * 60 * 60 * 24)); - let ano; - let mes; - - if ((ano = Math.floor(dia / 365)) > 0) - return <span className="text-sm font-light"> há {ano} {ano === 1 ? "ano" : "anos"} </span> - else if (((mes = Math.floor(dia / 31)) > 0)) - return <span className="text-sm font-light"> há {mes} {mes === 1 ? "mês" : "meses"}</span> - - if (dia <= 0) { - dia = 0 - return <span className="text-sm font-light"> hoje</span> - } - return <span className="text-sm font-light"> há {dia} {dia === 1 ? "dia" : "dias"}</span> - } + const [datePart, timePart] = updated_time.split(" "); + const [year, month, day] = datePart.split("-").map(Number); + const [hour, minute, secondWithMs] = timePart.split(":"); + const [second, ms] = secondWithMs.split(".").map(Number); + + const data = new Date(Date.UTC(year, month - 1, day, +hour, +minute, second, ms || 0)); + const dataAtual = new Date(); + const timeDiff = dataAtual.getTime() - data.getTime(); + + const dias = Math.floor(timeDiff / (1000 * 60 * 60 * 24)); + const anos = Math.floor(dias / 365); + const meses = Math.floor(dias / 30); + + if (anos > 0) + return <span className="text-sm font-light">há {anos} {anos === 1 ? "ano" : "anos"}</span>; + else if (meses > 0) + return <span className="text-sm font-light">há {meses} {meses === 1 ? "mês" : "meses"}</span>; + else if (dias === 0) + return <span className="text-sm font-light">hoje</span>; + + return <span className="text-sm font-light">há {dias} {dias === 1 ? "dia" : "dias"}</span>; + }; + return ( <> <div className="flex p-4 max-sm:p-2"> <div className="w-20 h-20 flex-shrink-0 rounded-full mr-7 max-sm:mr-0 max-sm:w-16"> - <Link href={`/perfil/${comment.user.id}`}> - {comment?.user?.avatar ? ( + <Link href={`/perfil/${comment.user_id}`}> + {comment?.user_id ? ( <Avatar - fill="true" - src={mecredURL + comment.user.avatar} + src={s3URL + `avatar/${comment.user_id}`} alt="Comment Avatar" - title={comment.user.name} + title={comment.user_id} sx={{ width: 80, height: 80 }} className="max-sm:w-10 max-sm:h-10" /> ) : ( - <div className={`flex items-center justify-center text-xl m-2 font-bold text-ice-HC-dark rounded-full h-20 w-20 max-sm:h-10 max-sm:w-10 ${getRandomBg(comment.user.id)}`} >{comment.user.name[0]}</div> + <div className={`flex items-center justify-center text-xl m-2 font-bold text-ice-HC-dark rounded-full h-20 w-20 max-sm:h-10 max-sm:w-10 ${getRandomBg(comment.user_id)}`} >{comment.author_name[0]}</div> )} </Link> </div> @@ -84,16 +85,16 @@ export default function PrintComments({ comment, userData, learningObjectId, com <div className="flex flex-col gap-1 w-full "> <div className="bg-ice-HC-dark rounded-lg p-4 outline outline-1 outline-ice-HC-white "> <div className="text-lg pb-2 text-darkGray-HC-white max-sm:text-sm"> - {comment.user.name} + {comment.author_name} </div> <div className="font-normal"> - {comment?.description} + {comment?.text} </div> </div> <div className="flex flex-row justify-between ml-1 mt-2 gap-2 rounded-lg "> {timeFunction(comment.updated_at)} - {(userData.id === comment.user.id) && + {(userData.id === comment.user_id) && <div className=" flex gap-2"> <button className="hover:bg-ice-HC-dark p-1 rounded-lg" onClick={() => setEdit(true)}> editar </button> <button className="hover:bg-ice-HC-dark p-1 rounded-lg" onClick={() => setOpenDeleteModal(true)}> excluir </button> diff --git a/src/app/recurso/[id]/components/resourcePreview.js b/src/app/recurso/[id]/components/resourcePreview.js index 5a8197d1f69fe9502cbf2ec133bf631201978b11..67543e5aa29780b64271d7b52cffb01d29770875 100644 --- a/src/app/recurso/[id]/components/resourcePreview.js +++ b/src/app/recurso/[id]/components/resourcePreview.js @@ -1,6 +1,6 @@ import Image from "next/image"; import { imageType, videoType, audioType } from "@/app/components/FileTypeUtilities"; -import mecredApi, { mecredURL } from "@/axiosConfig"; +import mecredApi, { mecredURL, s3URL } from "@/axiosConfig"; import OpenInNewOutlinedIcon from "@mui/icons-material/OpenInNewOutlined"; const youtubeURLtoID = (url) => { @@ -54,7 +54,9 @@ const getDefaultThumbnail = (type) => { export default function ResourcePreview({ learningObject, setSource, noFixed }) { if (!learningObject?.id) return <></>; - const uri = mecredURL + "inline/" + learningObject.id; + // const uri = mecredURL + "inline/" + learningObject.id; + + const uri = s3URL + "resource/" + learningObject.id; let content = ( <div className="relative aspect-video w-full h-full"> @@ -65,7 +67,7 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) width: "100%", objectFit: "cover", }} - src={getDefaultThumbnail(learningObject.object_type)} + src={getDefaultThumbnail(learningObject.objectTypeName)} alt={learningObject.name} /> </div> @@ -83,7 +85,7 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) className="aspect-video" ></iframe> ); - } else if (learningObject.default_mime_type === "application/pdf") { + } else if (learningObject.contentType === "application/pdf") { content = ( <object className="rounded-lg aspect-video" @@ -93,7 +95,7 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) height="100%" /> ); - } else if (videoType(learningObject?.default_mime_type)) { + } else if (videoType(learningObject?.contentType)) { content = ( <video src={uri} @@ -101,17 +103,17 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) className="flex rounded-lg justify-center items-center text-5xl aspect-video h-full w-full" /> ); - } else if (audioType(learningObject?.default_mime_type)) { + } else if (audioType(learningObject?.contentType)) { content = ( <div className="flex justify-center items-center h-full w-full"> <div> <audio preload="true" width="100%" height="100%" controls> - <source src={uri} type={learningObject.default_mime_type} /> + <source src={uri} type={learningObject.contentType} /> </audio> </div> </div> ); - } else if (imageType(learningObject?.default_mime_type)) { + } else if (imageType(learningObject?.contentType)) { content = ( <div className="relative bg-white-HC-dark aspect-video w-full h-full"> <img @@ -130,9 +132,11 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) if (typeof window !== "undefined") { let uri = mecredApi.getUri() + - `/learning_objects/${learningObject.id}/download`; - if (learningObject.default_mime_type === "application/pdf") { - uri = mecredURL + "inline/" + learningObject.id; + `public/resource/download/${learningObject.id}`; + if (learningObject.contentType === "application/pdf") { + // uri = s3URL + "inline/" + learningObject.id; + + uri = s3URL + "resource/" + learningObject.id; } window.open(uri); } @@ -147,7 +151,7 @@ export default function ResourcePreview({ learningObject, setSource, noFixed }) width: "100%", objectFit: "cover", }} - src={mecredURL + learningObject.thumbnail} + src={s3URL + `thumbnail/resource/${learningObject.id}`} alt={learningObject.name} /> diff --git a/src/app/recurso/[id]/components/specifications.js b/src/app/recurso/[id]/components/specifications.js index e3a8d8af6215ad1f392bcbd45675cfc7c5195bfe..eb0a093c71ed14a7faad7d8bb734b0f4187e729f 100644 --- a/src/app/recurso/[id]/components/specifications.js +++ b/src/app/recurso/[id]/components/specifications.js @@ -29,11 +29,11 @@ function formatSize(size) { export default function Specifications({ learningObject }) { const specInfo = [ - { icon: <IconLocation className='scale-[0.9] ml-1 mr-2 invertLogo-HC-white'/>, enabled: true, name: "Idioma", data: learningObject.language.map((lang) => lang.name).join(", ") ?? "Não Informado" }, - { icon: <IconLevel className='scale-[0.9] mr-2 invertLogo-HC-white'/>, enabled: true, name: "Nível de Ensino", data: learningObject.educational_stages.map((stage) => stage.name).join(", ") }, - { icon: <IconMedia className='scale-[0.75] rotate-[315deg] invertLogo-HC-white'/>, enabled: true, name: "Tipo de Mídia", data: learningObject.object_type }, - { icon: <IconTheme className='scale-[0.9] mr-1 invertLogo-HC-white'/>, enabled: true, name: "Temática", data: learningObject.subjects.map((subject) => subject.name).join(", ") }, - { icon: <IconSize className='scale-[0.9] mx-1 invertLogo-HC-white'/>, enabled: !learningObject.link, name: "Tamanho", data: formatSize(learningObject?.attachments[0]?.size) }, + { icon: <IconLocation className='mr-2 invertLogo-HC-white'/>, enabled: true, name: "Idioma", data: learningObject.languages.map((lang) => lang.name).join(", ") ?? "Não Informado" }, + { icon: <IconLevel className='mr-2 invertLogo-HC-white'/>, enabled: true, name: "Nível de Ensino", data: learningObject.educational_stages.map((stage) => stage.name).join(", ") }, + { icon: <IconMedia className='invertLogo-HC-white' style={{ transform: "scale(0.85)" }}/>, enabled: true, name: "Tipo de Mídia", data: learningObject.objectTypeName }, + { icon: <IconTheme className='mr-1 invertLogo-HC-white'/>, enabled: true, name: "Temática", data: learningObject.subjects.map((subject) => subject.name).join(", ") }, + // { icon: <IconSize className='mr-2 invertLogo-HC-white' style={{ transform: "scale(1.15)" }}/>, enabled: !learningObject.link, name: "Tamanho", data: formatSize(learningObject?.attachments[0]?.size) }, ]; return ( <div className="flex flex-col p-1 text-darkGray-HC-white"> diff --git a/src/app/recurso/[id]/page.js b/src/app/recurso/[id]/page.js index dbcce3d36b6cdabe04940ef4fc984cd297584e36..9c6316d7f781bc12a5dbe4f03dab8d7cf854d3b3 100644 --- a/src/app/recurso/[id]/page.js +++ b/src/app/recurso/[id]/page.js @@ -34,8 +34,6 @@ export default function Recurso({ params }) { const collectionId = searchParams.get("collectionId"); const token = getStoredValue("access_token"); - const client = getStoredValue("client"); - const uid = getStoredValue("uid"); useEffect(() => { const fetchData = async () => { @@ -44,15 +42,12 @@ export default function Recurso({ params }) { if (isLoggedIn()) { headers = { - "access-token": token, - "token-type": "Bearer", - client: client, - uid: uid, - Expires: 0, + Authorization:`bearer ${token}` }; } - const response = await mecredApi.get(`learning_objects/${params.id}`, { headers }); + const response = await mecredApi.get(`public/resource/${params.id}`); + setLearningObject(response.data); setState(response.data.state === "submitted"); setComplained(response.data.state === "suspended"); @@ -66,7 +61,8 @@ export default function Recurso({ params }) { }; fetchData(); - }, [params.id, client, token, uid]); + }, [params.id, token]); + useEffect(() => { setComplained(learningObject?.state === "suspended"); }, [learningObject]); diff --git a/src/app/sair/page.js b/src/app/sair/page.js new file mode 100644 index 0000000000000000000000000000000000000000..43687fa0c31ef45a4a06170a20c33b60354036e2 --- /dev/null +++ b/src/app/sair/page.js @@ -0,0 +1,21 @@ + +"use client" + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import Loading from '../components/Loading.js'; + +export default function Sair() { + const router = useRouter(); + + useEffect(() => { + localStorage.removeItem('token'); + localStorage.removeItem('user_data'); + localStorage.removeItem('provider'); + + router.push('/entrar'); + }); + + return <Loading loaded={false}></ Loading>; +} + diff --git a/src/app/styles/GovBrButton.css b/src/app/styles/GovBrButton.css new file mode 100644 index 0000000000000000000000000000000000000000..576d1a30751ce4319e3eadfbf442b1b1bb149bf5 --- /dev/null +++ b/src/app/styles/GovBrButton.css @@ -0,0 +1,283 @@ +.br-sign-in, +.br-button.sign-in { + --button-radius: 100em; + --button-xsmall: 24px; + --button-small: 32px; + --button-medium: 40px; + --button-large: 48px; + --button-size: var(--button-medium); + align-items: center; + background-color: transparent; + border: 0; + border-radius: var(--button-radius); + color: var(--interactive); + cursor: pointer; + display: inline-flex; + font-size: var(--font-size-scale-up-01); + font-weight: var(--font-weight-semi-bold); + height: var(--button-size); + justify-content: center; + overflow: hidden; + padding: 0 var(--spacing-scale-3x); + position: relative; + text-align: center; + vertical-align: middle; + white-space: nowrap; + width: auto; + --background: var(--gray-2); + --sign-in-img: 20px; + background-color: var(--background); + padding: 0 var(--spacing-scale-2x); +} +.br-sign-in.block, +.br-button.sign-in.block { + width: 100%; +} +@media (min-width: 576px) { + .br-sign-in.block-sm, + .br-button.sign-in.block-sm { + width: 100%; + } + .br-sign-in.auto-sm, + .br-button.sign-in.auto-sm { + width: auto; + } +} +@media (min-width: 992px) { + .br-sign-in.block-md, + .br-button.sign-in.block-md { + width: 100%; + } + .br-sign-in.auto-md, + .br-button.sign-in.auto-md { + width: auto; + } +} +@media (min-width: 1280px) { + .br-sign-in.block-lg, + .br-button.sign-in.block-lg { + width: 100%; + } + .br-sign-in.auto-lg, + .br-button.sign-in.auto-lg { + width: auto; + } +} +@media (min-width: 1600px) { + .br-sign-in.block-xl, + .br-button.sign-in.block-xl { + width: 100%; + } + .br-sign-in.auto-xl, + .br-button.sign-in.auto-xl { + width: auto; + } +} +.br-sign-in.circle, .br-sign-in[circle], .br-sign-in.is-circle, +.br-button.sign-in.circle, +.br-button.sign-in[circle], +.br-button.sign-in.is-circle { + border-radius: 50%; + padding: 0; + width: var(--button-size); +} +.br-sign-in.xsmall, .br-sign-in[xsmall], .br-sign-in.is-xsmall, +.br-button.sign-in.xsmall, +.br-button.sign-in[xsmall], +.br-button.sign-in.is-xsmall { + --button-size: var(--button-xsmall); +} +.br-sign-in.small, .br-sign-in[small], .br-sign-in.is-small, +.br-button.sign-in.small, +.br-button.sign-in[small], +.br-button.sign-in.is-small { + --button-size: var(--button-small); +} +.br-sign-in.medium, .br-sign-in[medium], .br-sign-in.is-medium, +.br-button.sign-in.medium, +.br-button.sign-in[medium], +.br-button.sign-in.is-medium { + --button-size: var(--button-medium); +} +.br-sign-in.large, .br-sign-in[large], .br-sign-in.is-large, +.br-button.sign-in.large, +.br-button.sign-in[large], +.br-button.sign-in.is-large { + --button-size: var(--button-large); +} +.br-sign-in.primary, .br-sign-in[primary], .br-sign-in.is-primary, +.br-button.sign-in.primary, +.br-button.sign-in[primary], +.br-button.sign-in.is-primary { + --interactive-rgb: var(--color-dark-rgb); + background-color: var(--interactive-light); + color: var(--color-dark); +} +.br-sign-in.secondary, .br-sign-in[secondary], .br-sign-in.is-secondary, +.br-button.sign-in.secondary, +.br-button.sign-in[secondary], +.br-button.sign-in.is-secondary { + background-color: var(--background-light); + border: 1px solid var(--interactive); +} +.br-sign-in.danger, .br-sign-in[danger], .br-sign-in.is-danger, +.br-button.sign-in.danger, +.br-button.sign-in[danger], +.br-button.sign-in.is-danger { + background-color: var(--danger); + --interactive-rgb: var(--color-dark-rgb); + color: var(--color-dark); +} +.br-sign-in.success, .br-sign-in[success], .br-sign-in.is-success, +.br-button.sign-in.success, +.br-button.sign-in[success], +.br-button.sign-in.is-success { + background-color: var(--success); + --interactive-rgb: var(--color-dark-rgb); + color: var(--color-dark); +} +.br-sign-in.warning, .br-sign-in[warning], .br-sign-in.is-warning, +.br-button.sign-in.warning, +.br-button.sign-in[warning], +.br-button.sign-in.is-warning { + background-color: var(--warning); + --interactive-rgb: var(--color-light-rgb); + color: var(--color-light); +} +.br-sign-in.info, .br-sign-in[info], .br-sign-in.is-info, +.br-button.sign-in.info, +.br-button.sign-in[info], +.br-button.sign-in.is-info { + background-color: var(--info); + --interactive-rgb: var(--color-dark-rgb); + color: var(--color-dark); +} +.br-sign-in:disabled, +.br-button.sign-in:disabled { + cursor: not-allowed; +} +.br-sign-in:not(:disabled), +.br-button.sign-in:not(:disabled) { + --focus-offset: var(--spacing-scale-half); +} +.br-sign-in:not(:disabled):focus, +.br-button.sign-in:not(:disabled):focus { + outline: none; +} +.br-sign-in:not(:disabled).focus-visible, .br-sign-in:not(:disabled):focus-visible, +.br-button.sign-in:not(:disabled).focus-visible, +.br-button.sign-in:not(:disabled):focus-visible { + outline-color: var(--focus); + outline-offset: var(--focus-offset); + outline-style: var(--focus-style); + outline-width: var(--focus-width); +} +.br-sign-in:not(:disabled):not(:disabled):hover, +.br-button.sign-in:not(:disabled):not(:disabled):hover { + background-image: linear-gradient(rgba(var(--interactive-rgb), var(--hover)), rgba(var(--interactive-rgb), var(--hover))); +} +.br-sign-in:not(:disabled):not(:disabled):active, +.br-button.sign-in:not(:disabled):not(:disabled):active { + background-image: linear-gradient(rgba(var(--interactive-rgb), var(--pressed)), rgba(var(--interactive-rgb), var(--pressed))); +} +.br-sign-in.active, .br-sign-in.is-active, .br-sign-in[active], +.br-button.sign-in.active, +.br-button.sign-in.is-active, +.br-button.sign-in[active] { + --hover: var(--hover-dark); + background-color: var(--active); + color: var(--color-dark); +} +.br-sign-in.loading, +.br-button.sign-in.loading { + color: transparent !important; + cursor: progress; +} +.br-sign-in.loading:not(:disabled):hover, +.br-button.sign-in.loading:not(:disabled):hover { + background-image: none; +} +.br-sign-in.loading::before, +.br-button.sign-in.loading::before { + border-color: var(--interactive) var(--interactive) transparent; + border-style: solid; +} +.br-sign-in.loading.primary::before, .br-sign-in.loading.danger::before, .br-sign-in.loading.success::before, .br-sign-in.loading.info::before, +.br-button.sign-in.loading.primary::before, +.br-button.sign-in.loading.danger::before, +.br-button.sign-in.loading.success::before, +.br-button.sign-in.loading.info::before { + border-color: var(--background) var(--background) transparent; +} +.br-sign-in.inverted, .br-sign-in.is-inverted, .br-sign-in[inverted], .br-sign-in.dark-mode, +.br-button.sign-in.inverted, +.br-button.sign-in.is-inverted, +.br-button.sign-in[inverted], +.br-button.sign-in.dark-mode { + --color: var(--color-dark); + --color-rgb: var(--color-dark-rgb); + --text-color: var(--color-dark); + --interactive: var(--interactive-dark); + --interactive-rgb: var(--interactive-dark-rgb); + --visited: var(--visited-dark); + --hover: var(--hover-dark); + --pressed: var(--pressed-dark); + --focus-color: var(--focus-color-dark); + --focus: var(--focus-color-dark); + color: var(--interactive-dark); +} +.br-sign-in.inverted.primary, .br-sign-in.inverted[primary], .br-sign-in.inverted.is-primary, .br-sign-in.is-inverted.primary, .br-sign-in.is-inverted[primary], .br-sign-in.is-inverted.is-primary, .br-sign-in[inverted].primary, .br-sign-in[inverted][primary], .br-sign-in[inverted].is-primary, .br-sign-in.dark-mode.primary, .br-sign-in.dark-mode[primary], .br-sign-in.dark-mode.is-primary, +.br-button.sign-in.inverted.primary, +.br-button.sign-in.inverted[primary], +.br-button.sign-in.inverted.is-primary, +.br-button.sign-in.is-inverted.primary, +.br-button.sign-in.is-inverted[primary], +.br-button.sign-in.is-inverted.is-primary, +.br-button.sign-in[inverted].primary, +.br-button.sign-in[inverted][primary], +.br-button.sign-in[inverted].is-primary, +.br-button.sign-in.dark-mode.primary, +.br-button.sign-in.dark-mode[primary], +.br-button.sign-in.dark-mode.is-primary { + --interactive-rgb: var(--background-dark-rgb); + background-color: var(--interactive-dark); + color: var(--background-dark); +} +.br-sign-in.inverted.secondary, .br-sign-in.inverted[secondary], .br-sign-in.inverted.is-secondary, .br-sign-in.is-inverted.secondary, .br-sign-in.is-inverted[secondary], .br-sign-in.is-inverted.is-secondary, .br-sign-in[inverted].secondary, .br-sign-in[inverted][secondary], .br-sign-in[inverted].is-secondary, .br-sign-in.dark-mode.secondary, .br-sign-in.dark-mode[secondary], .br-sign-in.dark-mode.is-secondary, +.br-button.sign-in.inverted.secondary, +.br-button.sign-in.inverted[secondary], +.br-button.sign-in.inverted.is-secondary, +.br-button.sign-in.is-inverted.secondary, +.br-button.sign-in.is-inverted[secondary], +.br-button.sign-in.is-inverted.is-secondary, +.br-button.sign-in[inverted].secondary, +.br-button.sign-in[inverted][secondary], +.br-button.sign-in[inverted].is-secondary, +.br-button.sign-in.dark-mode.secondary, +.br-button.sign-in.dark-mode[secondary], +.br-button.sign-in.dark-mode.is-secondary { + background-color: var(--background-dark); +} +.br-sign-in.inverted.active, .br-sign-in.is-inverted.active, .br-sign-in[inverted].active, .br-sign-in.dark-mode.active, +.br-button.sign-in.inverted.active, +.br-button.sign-in.is-inverted.active, +.br-button.sign-in[inverted].active, +.br-button.sign-in.dark-mode.active { + --hover: var(--hover-light); + --interactive-rgb: var(--active-rgb); + background-color: var(--background-light); + color: var(--active); +} +.br-sign-in img, +.br-button.sign-in img { + max-height: var(--sign-in-img); +} +.br-sign-in.inverted, .br-sign-in.is-inverted, .br-sign-in[inverted], .br-sign-in.dark-mode, +.br-button.sign-in.inverted, +.br-button.sign-in.is-inverted, +.br-button.sign-in[inverted], +.br-button.sign-in.dark-mode { + --background: var(--background-dark); +} + +/*# sourceMappingURL=signin.css.map*/ \ No newline at end of file diff --git a/src/app/themes/theme_high_contrast.css b/src/app/themes/theme_high_contrast.css index eac8be81dfc12693c142038b1883a42ef0a95eb9..a4a5897fa05920875c3ef1bf20adbd1774e7727f 100644 --- a/src/app/themes/theme_high_contrast.css +++ b/src/app/themes/theme_high_contrast.css @@ -35,6 +35,10 @@ html[data-theme="theme_high_contrast"] { --mediumGray-HC-dark: #000000; --underline: underline; --lightGray-HC-dark: #000000; + --govBrBlue-HC-white: #ffffff; + --govBrBG-HC-dark: #000000; + --govBrHover-HC-dark: #0000; + --govBrActive-HC-white: #FFFF; --fundo: linear-gradient(rgba(0, 0, 0, 1), rgba(0, 0, 0, 1)), url("/images/fundo.webp"); diff --git a/src/axiosConfig.js b/src/axiosConfig.js index 624c2a5a0febf2401f9821a4d75cea8c767ae369..4a6f4c0ab978dbee5d56cce28313be3832dbdf31 100644 --- a/src/axiosConfig.js +++ b/src/axiosConfig.js @@ -3,8 +3,13 @@ import { saveToLocalStorage, } from "./app/handlers/localStorageHandler"; -export const mecredURL = "https://api.mecred.c3sl.ufpr.br/"; -export const mecredURLv1 = "https://api.mecred.c3sl.ufpr.br/v1"; +//export const mecredURL = "http://localhost:4000"; +//export const mecredURLv1 = "http://localhost:4000"; +export const mecredURL = "http://mecred.mec.gov.br"; +export const mecredURLv1 = "http://mecred.mec.gov.br"; + +const govBrLogOutRedirectUrl = "https://mecred.mec.gov.br"; +export const govBrLogOutURL = `https://sso.staging.acesso.gov.br/logout?post_logout_redirect_uri=${govBrLogOutRedirectUrl}` const wrapper = (method) => { return (...args) => { @@ -20,7 +25,7 @@ const wrapper = (method) => { const mecredApi = (() => { const api = axios.create({ - baseURL: "https://api.mecred.c3sl.ufpr.br/v1", + baseURL: "https://api.mecreddev.c3sl.ufpr.br/", }); const wrappedApi = { diff --git a/tailwind.config.js b/tailwind.config.js index 59d76b1ccafbbb3869f9577ce15ad5e04e96dc6c..54c709a2cc6c9ef096973a48630a4bfde37eaaae 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -71,6 +71,10 @@ module.exports = { "mediumGray-HC-white": 'var(--mediumGray-HC-white)', "mediumGray-HC-dark": 'var(--mediumGray-HC-dark)', "lightGray-HC-dark": 'var(--lightGray-HC-dark)', + "govBrBlue-HC-white": 'var(--govBrBlue-HC-white)', + "govBrBG-HC-dark": 'var(--govBrBG-HC-dark)', + "govBrHover-HC-dark": "var(--govBrHover-HC-dark)", + "govBrActive-HC-white": "var(--govBrActive-HC-white)" }, backgroundImage: { "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", @@ -79,6 +83,7 @@ module.exports = { "fundo": 'var(--fundo)', "logo": 'var(--logo)', "logo-square": 'var(--logo-square)', + "govBrBGHover": "linear-gradient(var(--govBrHover-HC-dark), var(--govBrHover-HC-dark)))" }, keyframes: { scrollHint: { diff --git a/thumbnail.png b/thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..49c12289a16465bb4401fa2e9aded6d866ec2176 Binary files /dev/null and b/thumbnail.png differ