From a08f77aa008a2436c5664ba8ab8e9f96a4b74e81 Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Tue, 17 Feb 2026 17:27:46 +0000 Subject: [PATCH] Autosave: 20260217-172745 --- config/__pycache__/settings.cpython-311.pyc | Bin 5552 -> 5619 bytes config/__pycache__/urls.cpython-311.pyc | Bin 1557 -> 1663 bytes config/settings.py | 4 + config/urls.py | 1 + core/__pycache__/admin.cpython-311.pyc | Bin 212 -> 1405 bytes core/__pycache__/models.cpython-311.pyc | Bin 209 -> 16152 bytes core/__pycache__/urls.cpython-311.pyc | Bin 347 -> 2200 bytes core/__pycache__/views.cpython-311.pyc | Bin 1364 -> 17173 bytes core/admin.py | 15 +- core/migrations/0001_initial.py | 46 ++ ...0002_profile_is_email_verified_and_more.py | 41 ++ core/migrations/0003_group_event_report.py | 55 +++ core/migrations/0004_message.py | 30 ++ ...emove_profile_avatar_url_profile_avatar.py | 22 + .../0006_post_comment_hiddenpost_reaction.py | 65 +++ ..._profile_accountability_streak_and_more.py | 38 ++ core/migrations/0008_follow.py | 28 ++ .../__pycache__/0001_initial.cpython-311.pyc | Bin 0 -> 2713 bytes ...is_email_verified_and_more.cpython-311.pyc | Bin 0 -> 1772 bytes .../0003_group_event_report.cpython-311.pyc | Bin 0 -> 3618 bytes .../__pycache__/0004_message.cpython-311.pyc | Bin 0 -> 1813 bytes ..._avatar_url_profile_avatar.cpython-311.pyc | Bin 0 -> 946 bytes ...omment_hiddenpost_reaction.cpython-311.pyc | Bin 0 -> 3482 bytes ...untability_streak_and_more.cpython-311.pyc | Bin 0 -> 2338 bytes .../__pycache__/0008_follow.cpython-311.pyc | Bin 0 -> 1713 bytes core/models.py | 211 ++++++++- core/templates/base.html | 93 +++- core/templates/core/about.html | 49 ++ core/templates/core/chat.html | 54 +++ core/templates/core/edit_profile.html | 68 +++ core/templates/core/inbox.html | 40 ++ core/templates/core/index.html | 437 ++++++++++++------ core/templates/core/onboarding.html | 36 ++ core/templates/core/profile_detail.html | 175 +++++++ core/templates/core/settings.html | 49 ++ core/templates/registration/login.html | 30 ++ core/templates/registration/signup.html | 56 +++ core/templatetags/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 170 bytes .../social_filters.cpython-311.pyc | Bin 0 -> 729 bytes core/templatetags/social_filters.py | 9 + core/urls.py | 32 +- core/views.py | 289 +++++++++++- static/css/custom.css | 283 +++++++++++- 44 files changed, 2084 insertions(+), 172 deletions(-) create mode 100644 core/migrations/0001_initial.py create mode 100644 core/migrations/0002_profile_is_email_verified_and_more.py create mode 100644 core/migrations/0003_group_event_report.py create mode 100644 core/migrations/0004_message.py create mode 100644 core/migrations/0005_remove_profile_avatar_url_profile_avatar.py create mode 100644 core/migrations/0006_post_comment_hiddenpost_reaction.py create mode 100644 core/migrations/0007_post_post_type_profile_accountability_streak_and_more.py create mode 100644 core/migrations/0008_follow.py create mode 100644 core/migrations/__pycache__/0001_initial.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0002_profile_is_email_verified_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0003_group_event_report.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0004_message.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0005_remove_profile_avatar_url_profile_avatar.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0006_post_comment_hiddenpost_reaction.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0007_post_post_type_profile_accountability_streak_and_more.cpython-311.pyc create mode 100644 core/migrations/__pycache__/0008_follow.cpython-311.pyc create mode 100644 core/templates/core/about.html create mode 100644 core/templates/core/chat.html create mode 100644 core/templates/core/edit_profile.html create mode 100644 core/templates/core/inbox.html create mode 100644 core/templates/core/onboarding.html create mode 100644 core/templates/core/profile_detail.html create mode 100644 core/templates/core/settings.html create mode 100644 core/templates/registration/login.html create mode 100644 core/templates/registration/signup.html create mode 100644 core/templatetags/__init__.py create mode 100644 core/templatetags/__pycache__/__init__.cpython-311.pyc create mode 100644 core/templatetags/__pycache__/social_filters.cpython-311.pyc create mode 100644 core/templatetags/social_filters.py diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce5584823cd4ebfccfc40c2b36df8beaee7db..eaf2d7080f23a0e72b58e41697f7442b827a0b5b 100644 GIT binary patch delta 166 zcmdm>{aKrLIWI340}yPTJ0)}aL|#e8M;q0zun48En0xK?p9Fhm)oFb6Yex@}^e z$|YFEmYbTAnW%q@6-;XyZl2E1&t!dz)7RC-(=k3Y$mbRpgc;=TA5z2vG`UC+MDPQN yUmP~M`6;D2sdhz~KrSN?7r)(nO7J$b)CV>OUeN~58@!@3*e~+RH*kRntV%8 ciYpDMk`aiD?`{4kc$=B~0)r3`6$t``0AzX;^Z)<= diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94ece283a83ff1af1d71f1b265c943eb37a..e14328a8f4d0e98bb5826f1fe545162eb61a3529 100644 GIT binary patch delta 165 zcmbQr^Ph)zIWI340}$+=J0-JrBCjN)-9+^TjD8bOq==>RFfpWZrm|-Vf)s!NkS(;C zk5Q7DQFyWj%L<`eoW8Cuo{sUMK|Z&*z|6^~S=_m0fJzyGxLAO3vMlR-W@g5~$@^Iy zO>VI8PcWNcHbdizl+F&1D>nWYgaa-L2V4P?ECCJPAozhDsF?8s1Bfh=0-6B;mxU{T delta 92 zcmey*GnI#TIWI340}xbw%g@xA$ScXnHBo&5qshb*DVq}*e={)(PL5$&F^89W$^TE66!HN(>_CdhZ(|%x(B;7oMyc&h6g-7Uh#nky%VJyN}+wYm5h0_xEq|8eyu`p|L1aT86DH#7M%RRB7b7doQ7dL{Y*Kg!DtPBR* zvA78VxAe$)p0d4I+Dep=y4-~938lO!WWKv5827zU=!saePMWh8 zd+`ROtl*`kpmgyw)`AqupZ^>ENn!}P&={oRwW)jorkIszLGf@NIb$4PpGz~ zXh7M3=f&4(kVYdbgER%{HZJGp%zm0x;4`@Z2rO+i2HG_#r3d8x-sSs%EbaaKKBQx< z^5(sEexFWv=ycbhbg@gWO8a4F^1(4KoT40?#F**Z=ZP_%QtW*?+o7{anMcZ?v|n^? uEh1$eDf39V)3@(a3i}k;GhM@=kGtfmw4ZdQ7LIY@6a^{p*FpO&SrZc24q%h_%ArAlm diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640edbdcec3e5c1744466c916e7acdd2763b..efbc4cef01b93877c4e497442a856149f14062b3 100644 GIT binary patch literal 16152 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 delta 151 zcmbPHcabrDIWI340}xFAk)LS^q#uJgFu(+5d=>&SrZc24q%h_%HmHb8ZyIjMF< a96&KfATE|=m|SQh$$f!A28fE-fI zNvSC!sS-)6X(FpK$*38URkI|g=15-6lY&|xMYTvuYKfH9GMQGV$&5NfDr!X(VgeRX z94AoX&*bHlAiQKfYyp$PNhC!wX`DiGB$L4s%0@CdoJRRbrhsKsjATkUgUXT2G|r-# zNTz~wXf~3m;sTnBWae=m)gqZ&xPlfUnMGVgOOeb+xP)#;GIwwpEk`nU@ibbAWbWY^ zv>M5L%&HVa^-Gaej`grzvK{o2^{}-|>MUCON~nMGngjKi4?WE~^OJhF)v*z-i+=nE z9iN-8`2_W%4`&9!eq1{>Y|BrWdb?}){ItEzUpVYoIQL6Z<$*`Ycde_H#6`BeB2sTZvjZKASxm|r{H%yD}*e0g@=er8c zaXK|ttN3a1Y$Cm%ouE7+3N3Qa*$MXHJO$1!{s$W>S17+z`Wc=|B}}VAOB|XHpoZSH zcKE2Nqg^FTwL;6>p%&pVPQ5~>xyM|DM^Frf&TzgO!SfO+w8CjgYqbo!Yne*FioVmj zZDXTlbS;hlL5NGwKYvkr@#rGqz>*6~9xPo$iec5k=Eb=Kt1hg1uo_}> zgD-|v2i9Cz^I$E+7B14GTMlfwu;sy4h}8yUwC2F33!5HnhS=goZS=r_Z5OsZ*q->| z;p+SIInwg*@vEO4*mq&ygZ!Sx(KRa;Z!ifhbH;GVn#EKkgW%%ROf&&LG9C&arj=wjOuhtznbm7p0!!dYw zXgG~89XNL3*n{ISx;*^))nfav7r-85vTTQkZj? za+#x;85x)uQW;ZNQkhd&*RU;PW?)zi#1N3q7{!vp9?YQ0@e(AU$#{#UAh9IlB_ouR zVUVAjdW)e5WD!U*FEKaOPm}c)cS=@bUV6S>X;Dsb5y-S#tYw+0<;6v;lMk{6#P9({ zKt>dM0f`UHjEsyo7-TM>q6ZA(7f{g$HU?JC2A2+@j*uB_7g*#kvdCXyk^jKV#Lv{g K4T42HK;r;Kc0X(Y diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd69370b38a98d8b01bf8eb9817c42f16ed6..15ad71e363e4ec52809c956341bb9f9a17bec62f 100644 GIT binary patch literal 17173 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 SYkC8E!C0?c&e94fYX2X5^aL>g literal 1364 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 E2T4f5{Qv*} literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0003_group_event_report.cpython-311.pyc b/core/migrations/__pycache__/0003_group_event_report.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9a82dd49ece4424ee686d56519d88748e4ee0df GIT binary patch literal 3618 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 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0006_post_comment_hiddenpost_reaction.cpython-311.pyc b/core/migrations/__pycache__/0006_post_comment_hiddenpost_reaction.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..afa4e1da3616f096344b5bd2ca15740cc27939a6 GIT binary patch literal 3482 zcmcInO-vg{6y9C`t}$RI5Mo0}0_4Yp;0TpORjVpM_(=Q*MsJp~|UmW-){SNj019q|*1_YW9j^-l3NuS!%4_S;J*wVtFfX$CXjR2LMK)^oG5~f_ zW>>pww^6${Pq3+DHdVwNu3wB*?H9$JQG91~tU5yVujAi{y%}8zpg20F@G8G?eD54Y z^eyf()9@cM^S>hAgST;AfU3adHDL0@k(q2lClBE-`VRah-h;p7k#lU`gMMGNKLGv7 zjeT4yKGcFv<=(z_THgcvX@>oHwPFf~&iulov#4#Oeee4GpjvhuR7-SZ-uJM+pC>=? zbnYOY6n~}xorfLp0ujfmyB%zO{;XkQa{0=YE4`A5^F|3v1=CniH7v;`*_KVIXlie$ zh=0PcY$B*=dWZ-&Z>0PrDi>`-%9>cVF(NeuQ_aifinN4Rh-hI`l{Ix0lV}N>^M-{b zUCv_?${MUYqGvX{CZK-h*&IWhKwZJAfm12v1QBo0&L)3 zNZnYLWQ0f%637`Q2{0$%Lo{>=Hgar3SQBeBFE6YBj>3kCAS+#2ja=UK>4S(gvEYJ= zHDr;HQSi=$(14N8L#8RgOu}+8TR;#$jYPs(Z^~K~DU;ny85vO#!ukX?@D~9;iRPF_ zV%d@UqORU8Vt}MzJBLm1dye{TqkhAaSkBV8q!#F8BgtMV;FJ%_706-P1ja^aN6V+F zOPKgaLDE?(4}~LvDcYHx;O%(>2w)?W%^7MI115wQJjR?5ec z@X|@av*W2o5|$Wql0+hs1jl0$7KcQll61EyYm_lbGU;(e8mwixP>|qAZ+Ne~GIkeMO#1VL33}dH4}qKeJD*CB6Z`pc_`(knH+raqtn#+pyW1nKJ`EAa59pkn@*Fp*<`s*mJ_qf@ubtz zwKn9obZxeDyDi;LEM1PDbds0W&b!G=k2{`DKbv)uGn>g7H#y_PZkOXNPHWHFg4^2j zr{8HE+-x0mTL+!kP&uA(n!DDny3JjWOHWqZYs2of3HREpqv($H?H0$`{HRdjxRx>D z1(TqHF=*oBLZv3yP*;g@@#eK}*2XJ7p|P%VJSs+Ba3Hp*s3dn`C65T}eKWs<-8)66 zxwP3_a+^y|Y#A7J5}j)&+(hT&nI~Vny@PJ=xZ6A9V8gM$11QNp3JxTI14+U4zYcD0 zZR~B}PQ3%T!?Lg-;0n*8ukc^d@X1M*&?HM}k|i`H913G@!AU5a3B^q)PApdm`JpIL zF5c#JeB`wEdYRWpXjwpPQBgUuTbAZ`$o!1c*7Ngux2?zdr09Ji$?L)}&1_njp5F}{gvxldQ3t&(J%+MwK8Q6XX7I=pz2M&L7F6+)&PQuJ2&(21=BW?5GM0;$P%2><{9 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0007_post_post_type_profile_accountability_streak_and_more.cpython-311.pyc b/core/migrations/__pycache__/0007_post_post_type_profile_accountability_streak_and_more.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..60740c25abee3aad2bb7bed094ee5eb59fd2d9c8 GIT binary patch literal 2338 zcmbtVJ#5=X6ecN&qA1z26gkSj-86qn3d>0V2PoR2iftus^Virx0WAkwcamsRq;jO3 z*i(iM-I}35GiOLsL_>!T9XocU0S>}V1v+J85D!JBzB@`vlo}n{llb`l?t6Fd-aGy{ zF`-K^zI*z;c}ta~Ke;d*i2?Ka4lqARSi-U;)ns@pmeP*ZVlvmotav+7OSILRDocuV zO~UbSB%C0LjpPvMSxk~%z!QyH63eSnNqx!1TP2xkF7Z6mZn`Cf>20$~4bOCJm#J+B z6U(g+=84zjKzRL^%-2N3dTI)m6^X<);zL)64!eoQGw^%?Pc)iJEh&7pp$02B2Ab4{ z$OP9mVGmDboWN=?IllIuHZH%1l)}0|N{_J9z07$0eGxJv{|P)P5GF>NEY9L7oa^PE z!R}s+cGytx0pU5!h=jAf!gwt3nHE0TAsU(5$PM|8)QHQExC28DAAC25z2f-30r}8> zMn3%S$jvxDf{(UjQ|=ubkJ^mY3ivqG&Iy*fIjj>Xnt0uzgq@zBpI<;7$MsO%X}5{( zp_Yj;v4x5fqt45U#kt5*Lg{UHNJ-^oM@hpXk%NAB#DgVmInE{&Cu%r!Cw(IlW~&Yb zo8~kcgo2T2w+9?(Gh~a$Htj9bBRiR8F3qk839Q);b()m8F6wr$0S?(a1I6q;q2Mvy zP1CZF+cGHGNw0EYc9jd17s|^5kW!2#>n+Eu6PG11X&7D0gXS?FB8%8fuLX8pv=6e4 zHeqo=6AhDCm?dEe4bviQ+Njr^uI(A?re%8D$n_vgn^fWWGCc?{rY}0SJxGjV;pn z(KE0SdO;+O9_E`;MIIn=PDk7{w@TAYLjo8frXvKI>skc(3_|z2hQ+go5arFs3ho1= z(=pb;j!B2uP+;|K%5OK8t=wC?j_$24-$gfXFD>6-nURMH=cH9IuJRM0{AOjTML1F+ zv&@R^ktP{P=|z#&oBZ};nX6{A0=bSHCzc=uYi657q7HRP%%=S%q+P`uhTU|Sg4dZQ za>lDYxma0UtSl`v-RY3Q#rl)<8sbZD+U1thCgturlt8yk9+Eo5q2&h--E=!fos@Z7 zmET^-WoWP~QG<=T2MuZo?ORI7urVZ)lDW<{<(~sV_|5C~;6{El`h}{L`na#1{ZPmCD1BiB34iz-3T*M>KL~j?H7;tbI1DmlaJ2?`IDg}ePqYpqYq1Z0nsP=5*o&nM=m@$6CAnlbK@5?Sf~aIw}XYR{e}mN-jS~=u-+Z{t^(50RRy*H6jVS#lUq~% zH$a$56ehz-DRd z^xUxE=%?Vu(gQnmb0cbv)M$q2BGCa+MO@{$0(=R$H`*sr^L<9IgUCPIt_LKH$+8Sp pB7fjZv(djlWZu!7^QC?Mp?X4AL+PL7u^vpFdc_TU`&h-Q{s!jCgV+E7 literal 0 HcmV?d00001 diff --git a/core/migrations/__pycache__/0008_follow.cpython-311.pyc b/core/migrations/__pycache__/0008_follow.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6456005074a02a890feea5d4646ad36978f76d5e GIT binary patch literal 1713 zcmbVM&2Jk;6rUa2oAp-`Cn+I9iD`+{xDs}x93ZGvm^dz_NsF2kAww9gJrif0^{zX+ zPB0QE2M!#2K%#e~080LV9yoA995>P)qB$XP;WMeIw%drpftlU6Z{EE3o44;{ ze#~Z52*z(ed}n+TN9a$v7>q=pIsXWl?-4~*F;Pu{FE(*IR*NZ87Bl1RL@m))YpQ}U zx{YZ38$=T<(Ha>59mWtkf;Sqq5vpvUg8G+S-YY0VbC~NIR?{h9k!l-FuDgb9IYMpQ zl$lPWZx?5LNSejB-sih zA*D19GeOnkkwZW{8lrAvs)d^Ap>6y)c^MycQlZYkQ^c~Z(Sd#|IpoHM-1vZ_=@MKg zb#2lBMVC>L?r@{6^B&n{J&|-6H+0kZl8MY7<8|9%#M0YLr26VaccGXf1k2thI;BF} z4$lCri9W{(XJ%Ajg_ua%7J(XLE`Ww|Mk7${+CyV5CL?30(dX7;tZNyMx{SDXles&L z7chSnGRY@_0CYTbPTe+`NgXj_cfz8+SC|kQ3C|!zqzHt9b|F?oWC(fG)lF$d2$wY{ zrks7f)6wg&Pg#dqlv#})myIOG$`3a`AP+ZIACUF?mDM$o8EP2J5$TmMgzK__xa?z* zdEaQ3A-GlPkH}Uaz?(*!Maxtb5@Iy1yAWrUwsfm$3ry=m3u6P#C`MMw8!P3?sz})# z)^E_C(K#TaXSYf_cAJ&Dbx3x%#J*q+IER<^ZNBSt^ad-*QYk%e>=G2s+awBUvD4%7 zo(PF-N#`20>cMaZDoWm0PPOabrhM)CPny5*-icQBwQ`V%O{oWKK?WuBa&PW5H}gmC ziZ^}Z@t!|@<7xc&d9Ox1i+j_~$+Y87JKnf^nw$0VOHcOv{L-&eUcP#gulo6_H-7sx z_nJ3beDbkBTYUP;FEjq~9e??Ozg+W-uL6Wt@LhZ%Kc8Y)&3%T?LJA__L6Y0T{|@jq zC{*y81gPLpeQqlR*pedPCBWx6NG7I6gHe>b=FQ*q-gq~N;VYv-E}cxBAs`o$1hMRd zdhnr~yf_G2K@&4k%8ImJjIPY$Aa8sD)I_?!->|LisC<$`i?Gtd5{dHgro;`wOTazT zZbk)qi!Z@a-lC2RB#0@B0=ZGHdg%4&IUO)BNESTwk|$876g5EqB#%@6`23mNxOmBK G=+)n1RLkN3 literal 0 HcmV?d00001 diff --git a/core/models.py b/core/models.py index 71a8362..a5db98c 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,212 @@ from django.db import models +from django.contrib.auth.models import User -# Create your models here. +class Intent(models.Model): + name = models.CharField(max_length=100) + icon = models.CharField(max_length=50, blank=True, help_text="Bootstrap icon class name") + + def __str__(self): + return str(self.name) + +class ValueTag(models.Model): + name = models.CharField(max_length=100) + + def __str__(self): + return str(self.name) + +class Profile(models.Model): + TRANSITION_CHOICES = [ + ('none', 'Stable'), + ('post-divorce', 'Post-Divorce'), + ('relocating', 'Relocating'), + ('career-change', 'Career Change'), + ('new-in-town', 'New in Town'), + ] + + user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='profile') + professional_headline = models.CharField(max_length=255, blank=True) + transition_status = models.CharField(max_length=50, choices=TRANSITION_CHOICES, default='none') + bio = models.TextField(blank=True) + location_city = models.CharField(max_length=100, blank=True) + intents = models.ManyToManyField(Intent, blank=True) + value_tags = models.ManyToManyField(ValueTag, blank=True) + avatar = models.ImageField(upload_to='avatars/', blank=True, null=True) + + # Momentum & Engagement + accountability_streak = models.IntegerField(default=0) + + # Auth & Security + is_email_verified = models.BooleanField(default=False) + two_factor_enabled = models.BooleanField(default=False) + onboarding_completed = models.BooleanField(default=False) + + # Multitenancy (Future Proofing) + organization_id = models.IntegerField(null=True, blank=True) + + @property + def get_avatar_url(self): + if self.avatar and hasattr(self.avatar, 'url'): + return self.avatar.url + return f"https://i.pravatar.cc/150?u={self.user.username}" + + @property + def connection_count(self): + return Connection.objects.filter(models.Q(user1=self.user) | models.Q(user2=self.user)).count() + + @property + def following_count(self): + return Follow.objects.filter(follower=self.user).count() + + @property + def followers_count(self): + return Follow.objects.filter(followed=self.user).count() + + @property + def events_attended_count(self): + return self.user.attending_events.count() + + @property + def profile_completion_percentage(self): + steps = 0 + total_steps = 5 + if self.professional_headline: steps += 1 + if self.bio: steps += 1 + if self.location_city: steps += 1 + if self.intents.exists(): steps += 1 + if self.avatar: steps += 1 + return int((steps / total_steps) * 100) + + def __str__(self): + return f"{self.user.username}'s Profile" + +class Connection(models.Model): + user1 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='connections1') + user2 = models.ForeignKey(User, on_delete=models.CASCADE, related_name='connections2') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('user1', 'user2') + + def __str__(self): + return f"{self.user1.username} <-> {self.user2.username}" + +class Follow(models.Model): + follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='following_relations') + followed = models.ForeignKey(User, on_delete=models.CASCADE, related_name='follower_relations') + created_at = models.DateTimeField(auto_now_add=True) + + class Meta: + unique_together = ('follower', 'followed') + + def __str__(self): + return f"{self.follower.username} follows {self.followed.username}" + +class Group(models.Model): + name = models.CharField(max_length=255) + description = models.TextField() + is_private = models.BooleanField(default=False) + moderators = models.ManyToManyField(User, related_name='moderated_groups') + members = models.ManyToManyField(User, related_name='joined_groups') + organization_id = models.IntegerField(null=True, blank=True) + + def __str__(self): + return str(self.name) + +class Event(models.Model): + title = models.CharField(max_length=255) + description = models.TextField() + start_time = models.DateTimeField() + end_time = models.DateTimeField() + location = models.CharField(max_length=255) + group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='events', null=True, blank=True) + organizer = models.ForeignKey(User, on_delete=models.CASCADE) + attendees = models.ManyToManyField(User, related_name='attending_events') + organization_id = models.IntegerField(null=True, blank=True) + + def __str__(self): + return str(self.title) + +class Report(models.Model): + reporter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reports_made') + reported_user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reports_received', null=True, blank=True) + content = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + resolved = models.BooleanField(default=False) + organization_id = models.IntegerField(null=True, blank=True) + + def __str__(self): + return f"Report by {self.reporter.username} at {self.timestamp}" + +class Message(models.Model): + sender = models.ForeignKey(User, on_delete=models.CASCADE, related_name='sent_messages') + recipient = models.ForeignKey(User, on_delete=models.CASCADE, related_name='received_messages') + body = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + is_read = models.BooleanField(default=False) + + class Meta: + ordering = ['timestamp'] + + def __str__(self): + return f"From {self.sender.username} to {self.recipient.username} at {self.timestamp}" + +class Post(models.Model): + POST_TYPE_CHOICES = [ + ('reflection', 'Reflection'), + ('looking_for', 'Looking For'), + ('offering', 'Offering'), + ('event_invite', 'Event Invite'), + ('progress_update', 'Progress Update'), + ('skill_share', 'Skill Share'), + ] + author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='posts') + content = models.TextField() + image = models.ImageField(upload_to='posts/', blank=True, null=True) + post_type = models.CharField(max_length=20, choices=POST_TYPE_CHOICES, default='reflection') + timestamp = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['-timestamp'] + + def __str__(self): + return f"Post by {self.author.username} at {self.timestamp}" + + def user_has_reacted(self, user, reaction_type='heart'): + if user.is_authenticated: + return self.reactions.filter(user=user, reaction_type=reaction_type).exists() + return False + +class Comment(models.Model): + post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') + author = models.ForeignKey(User, on_delete=models.CASCADE) + content = models.TextField() + timestamp = models.DateTimeField(auto_now_add=True) + + class Meta: + ordering = ['timestamp'] + + def __str__(self): + return f"Comment by {self.author.username} on {self.post}" + +class Reaction(models.Model): + REACTION_CHOICES = [ + ('heart', 'Heart'), + ('like', 'Like'), + ('smile', 'Smile'), + ] + post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='reactions') + user = models.ForeignKey(User, on_delete=models.CASCADE) + reaction_type = models.CharField(max_length=20, choices=REACTION_CHOICES, default='heart') + + class Meta: + unique_together = ('post', 'user', 'reaction_type') + + def __str__(self): + return f"{self.user.username} {self.reaction_type}ed {self.post}" + +class HiddenPost(models.Model): + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='hidden_posts') + post = models.ForeignKey(Post, on_delete=models.CASCADE) + + class Meta: + unique_together = ('user', 'post') diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..0856475 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -3,23 +3,98 @@ - {% block title %}Knowledge Base{% endblock %} + + {% block title %}CommonGround{% endblock %} {% if project_description %} - - - {% endif %} - {% if project_image_url %} - - {% endif %} {% load static %} - + + + + + + {% block head %}{% endblock %} - {% block content %}{% endblock %} + + +
+ {% block content %}{% endblock %} +
+ + + + + + + {% block scripts %}{% endblock %} diff --git a/core/templates/core/about.html b/core/templates/core/about.html new file mode 100644 index 0000000..522207c --- /dev/null +++ b/core/templates/core/about.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} + +{% block title %}About - CommonGround{% endblock %} + +{% block content %} +
+
+
+
+

Our Mission

+

CommonGround is a platform for intentional, platonic connections for professionals and individuals navigating life transitions.

+

Not a dating app — a high-trust social connection ecosystem centered on shared values, life goals, activities, and authentic engagement.

+
+
+
+
+ +
+
+
+
+
+

High Trust

+

Verified members and moderated communities ensure a safe and respectful environment.

+
+
+
+
+

Value Centered

+

Match based on shared values like growth, community, and adventure.

+
+
+
+
+

Intent Driven

+

Clearly state what you're looking for: networking, friendship, or activity partners.

+
+
+
+
+
+ +
+
+

Ready to find your community?

+ Join CommonGround +
+
+{% endblock %} diff --git a/core/templates/core/chat.html b/core/templates/core/chat.html new file mode 100644 index 0000000..b927301 --- /dev/null +++ b/core/templates/core/chat.html @@ -0,0 +1,54 @@ +{% extends "base.html" %} +{% block title %}Chat with {{ partner.first_name }} | CommonGround{% endblock %} + +{% block content %} +
+
+
+
+
+ + {{ partner.username }} +
+
{{ partner.get_full_name|default:partner.username }}
+ Active now +
+
+ +
+ {% for message in chat_messages %} +
+
+

{{ message.body }}

+ + {{ message.timestamp|date:"H:i" }} + +
+
+ {% empty %} +
+

No messages yet. Say hello to {{ partner.first_name }}!

+
+ {% endfor %} +
+ + +
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/edit_profile.html b/core/templates/core/edit_profile.html new file mode 100644 index 0000000..f5d718f --- /dev/null +++ b/core/templates/core/edit_profile.html @@ -0,0 +1,68 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+
+
+

Edit Profile

+ +
+ {% csrf_token %} + +
+
+ Current Avatar + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/inbox.html b/core/templates/core/inbox.html new file mode 100644 index 0000000..83f5695 --- /dev/null +++ b/core/templates/core/inbox.html @@ -0,0 +1,40 @@ +{% extends "base.html" %} +{% block title %}Messages | CommonGround{% endblock %} + +{% block content %} + +{% endblock %} diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..7a9db2a 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,302 @@ {% extends "base.html" %} +{% load static %} +{% load social_filters %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}Dashboard | CommonGround{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +{% if user.is_authenticated %} +
+
+ + + + +
+ +
+

Welcome back, {{ user.first_name|default:user.username }}

+
+ {{ user.profile.get_transition_status_display }} + {{ user.profile.location_city|default:"Location not set" }} +
+
+ + +
+
+
+ {% csrf_token %} +
+ +
+ + +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + +
+
+
Suggested Members
+ View All +
+
+ {% for profile in suggested_members %} + + {% endfor %} +
+
+ + +
+
Activity Feed
+ {% for post in posts %} +
+
+ {{ post.get_post_type_display }} +
+
+ +
+

{{ post.author.get_full_name|default:post.author.username }}

+

{{ post.timestamp|timesince }} ago

+
+
+ +
+

{{ post.content }}

+ {% if post.image %} + + {% endif %} + + +
+ + {{ post.reactions.count }} + + +
+ + +
+
+ {% for comment in post.comments.all %} +
+ +
+
+

{{ comment.author.username }}

+

{{ comment.content }}

+
+
+
+ {% endfor %} + +
+ {% csrf_token %} +
+ + +
+
+
+
+
+
+ {% empty %} +
+

No posts yet. Start the conversation!

+
+ {% endfor %} +
+
+ + +
+ +
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
-
- Page updated: {{ current_time|date:"Y-m-d H:i:s" }} (UTC) -
-{% endblock %} \ No newline at end of file + + +{% else %} + +
+
+
+
+

Intentional connections for life's transitions.

+

CommonGround helps professionals build high-trust, platonic relationships based on shared values and life goals.

+ +
+
+
+
+ +
+
+
+
+

Discovery Feed

+

Members aligned with current goals and locations.

+
+
+ All Aligned + {% for intent in intents %} + {{ intent.name }} + {% endfor %} +
+
+ +
+ {% for profile in profiles %} +
+
+
+ {{ profile.user.get_full_name }} +
+ {{ profile.get_transition_status_display }} +

{{ profile.user.first_name }} {{ profile.user.last_name|slice:":1" }}.

+

{{ profile.location_city }}

+
+
+

{{ profile.professional_headline }}

+

{{ profile.bio }}

+ +
+ {% for intent in profile.intents.all %} + {{ intent.name }} + {% endfor %} +
+ + +
+
+ {% empty %} +
+

No profiles found yet.

+
+ {% endfor %} +
+
+
+{% endif %} +{% endblock %} diff --git a/core/templates/core/onboarding.html b/core/templates/core/onboarding.html new file mode 100644 index 0000000..6bd7f04 --- /dev/null +++ b/core/templates/core/onboarding.html @@ -0,0 +1,36 @@ +{% extends "base.html" %} + +{% block title %}Onboarding - CommonGround{% endblock %} + +{% block content %} +
+
+
+

Welcome to CommonGround

+

Let's set up your profile to help you find the right connections.

+
+
+ +
+
+
+
+ {% csrf_token %} +
+ + +
What do you do? (Keep it professional and clear)
+
+ +
+ + +
+ + +
+
+
+
+
+{% endblock %} diff --git a/core/templates/core/profile_detail.html b/core/templates/core/profile_detail.html new file mode 100644 index 0000000..ba90725 --- /dev/null +++ b/core/templates/core/profile_detail.html @@ -0,0 +1,175 @@ +{% extends 'base.html' %} +{% load social_filters %} + +{% block content %} +
+
+
+
+
+
+ {{ target_user.username }} +
+
+ +
+

{{ target_user.first_name }} {{ target_user.last_name }}

+

@{{ target_user.username }}

+ + {% if target_user.profile.professional_headline %} +
{{ target_user.profile.professional_headline }}
+ {% endif %} + +
+ + {{ target_user.profile.location_city|default:"Location not set" }} + + + {{ target_user.profile.get_transition_status_display }} + +
+ +
+ {{ target_user.profile.bio|linebreaks }} +
+ + {% if request.user == target_user %} + + {% else %} +
+ {% if is_following %} + + Following + + {% else %} + + Follow + + {% endif %} + + Message + +
+ {% endif %} +
+ + +
+
+
+
+

Recent Activity

+ {% for post in target_user.posts.all %} +
+
+ {{ post.get_post_type_display }} +
+
+ +
+

{{ post.author.get_full_name|default:post.author.username }}

+

{{ post.timestamp|timesince }} ago

+
+
+ {% if post.author == user %} + + {% endif %} +
+

{{ post.content }}

+ {% if post.image %} + + {% endif %} + +
+ + {{ post.reactions.count }} + + +
+ +
+
+ {% for comment in post.comments.all %} +
+ +
+
+

{{ comment.author.username }}

+

{{ comment.content }}

+
+
+
+ {% endfor %} +
+
+
+
+ {% empty %} +
+

No posts yet.

+
+ {% endfor %} +
+
+
+{% endblock %} diff --git a/core/templates/core/settings.html b/core/templates/core/settings.html new file mode 100644 index 0000000..a1e1039 --- /dev/null +++ b/core/templates/core/settings.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} + +{% block title %}Settings - CommonGround{% endblock %} + +{% block content %} +
+
+
+
+

Account Settings

+ +
+
+
+
+

Preferences

+
+ {% csrf_token %} +
+ +
+ + +
+
+ +
+ + {% if profile.is_email_verified %} + Verified + {% else %} + Pending +

Verification is simulated for MVP.

+ {% endif %} +
+ + +
+
+
+
+
+{% endblock %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..5f8b0a6 --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,30 @@ +{% extends "base.html" %} + +{% block title %}Log In - CommonGround{% endblock %} + +{% block content %} +
+
+
+
+

Welcome back

+
+ {% csrf_token %} +
+ + +
+
+ + +
+ +
+
+

Don't have an account? Sign up

+
+
+
+
+
+{% endblock %} diff --git a/core/templates/registration/signup.html b/core/templates/registration/signup.html new file mode 100644 index 0000000..a1a44c1 --- /dev/null +++ b/core/templates/registration/signup.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} + +{% block title %}Sign Up - CommonGround{% endblock %} + +{% block content %} +
+
+
+
+

Create your account

+
+ {% csrf_token %} + {% for field in form %} +
+ + {{ field }} + {% if field.help_text %} +
{{ field.help_text }}
+ {% endif %} + {% for error in field.errors %} +
{{ error }}
+ {% endfor %} +
+ {% endfor %} + +
+
+

Already have an account? Log in

+
+
+
+
+
+{% endblock %} + +{% block head %} + +{% endblock %} diff --git a/core/templatetags/__init__.py b/core/templatetags/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/templatetags/__pycache__/__init__.cpython-311.pyc b/core/templatetags/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..15938f1978c75d3a1227d5a8756590a658210f12 GIT binary patch literal 170 zcmZ3^%ge<81S$)sWP<3&AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFI)YL{M=Oi z(xlS7l2ZNDiqz!NlKdk5^8BLg;)2BFRQ=@qqE!8o)ZBuc#FEsK#PnkQ`1s7c%#!$c oy@JYL95%W6DWy57c15f}(?IqW^8<+w%#4hT9~fXn5i?K>0NU#*lK=n! literal 0 HcmV?d00001 diff --git a/core/templatetags/__pycache__/social_filters.cpython-311.pyc b/core/templatetags/__pycache__/social_filters.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..68ee537a68151b29d3bf5a414185df61c9749aed GIT binary patch literal 729 zcmZ8fziZn-6uy&WS&48jC8HZ0h!zd4rlu~GE`?4ZOQvQqvhM20)em!bqA{3aN}(A# zbSQLnmL#S5W9%S^xyjHj-CVL|>YW^!xDVaa_a5JS@6$V-*J|rPntS>!{4L+VR8X3J z2W}Dxz5@&}j6e(zAp{yAu&b5CGYNi{0XWo&J~Ec!J%F`K{R~R3JPX6dC-7f&1y>G$ zYyMFL7Yd7$xEJ}Hh;>T*fD_zm_hsHl{Fnqw36iYjs&glI0J}0+7=1hd-R02H{i>1z z2JjNDiW`*+SaK9tm3siNCgtk;N>auMeeD`zBV;W<<3}XnVIZYru~ujg(}W50Z5VMv zg-MP>#+j=Lqn9#HtFkv!%C@&}UwKDqOuVd}B|P)UF$prBQty3A-?5$_5HCn6@yg9` zzr#G12B9Cd7R}j{UZ1KfC|pH#ekR=z%&K-?eKZHK@_dRmzkIkx&J}VloH24H$jgy; zjdril?gTY+)EryQsl9RX^hTC~eb*Ew?)ph5rD`vtx)-*oPy526q$9tCP^x~VV&V@# zA1GXt?$&w{r#OqqOS&mTbqnl`baNd-I0g0b>NB(JC(YchPwcJS-Wpmnxc0g6vGFw; bHYRW/", chat_detail, name="chat_detail"), + path("profile/", profile_view, name="my_profile"), + path("profile/edit/", edit_profile, name="edit_profile"), + path("profile//", profile_detail, name="profile_detail"), + path("profile//follow/", toggle_follow, name="toggle_follow"), + + # Social URLs + path("post/create/", create_post, name="create_post"), + path("post//delete/", delete_post, name="delete_post"), + path("post//comment/", add_comment, name="add_comment"), + path("post//react/", toggle_reaction, name="toggle_reaction"), + path("post//hide/", hide_post, name="hide_post"), + + # Auth URLs + path("accounts/", include("django.contrib.auth.urls")), ] diff --git a/core/views.py b/core/views.py index c9aed12..4574534 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,284 @@ import os import platform -from django import get_version as django_version -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 from django.utils import timezone - +from .models import Profile, Intent, ValueTag, Message, Post, Comment, Reaction, HiddenPost, Follow +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth import login +from django.contrib.auth.decorators import login_required +from django.db.models import Q def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + """Render the landing screen or member dashboard.""" + + # Simple logic to seed data for the first run + if not Intent.objects.exists(): + intents_data = [ + ('Friendship', 'bi-people'), + ('Networking', 'bi-briefcase'), + ('Activity Partner', 'bi-bicycle'), + ('Accountability', 'bi-check-circle') + ] + for name, icon in intents_data: + Intent.objects.create(name=name, icon=icon) + + if not Profile.objects.exists(): + # Create a demo user/profile + demo_user, _ = User.objects.get_or_create(username='marcus_v', first_name='Marcus', last_name='V.') + p = Profile.objects.create( + user=demo_user, + professional_headline='Architect & Urban Planner', + transition_status='new-in-town', + bio='Passionate about sustainable cities. Recently moved here from Chicago and looking for local communities.', + location_city='Austin, TX', + ) + p.intents.add(Intent.objects.get(name='Networking')) + + demo_user2, _ = User.objects.get_or_create(username='sarah_l', first_name='Sarah', last_name='L.') + p2 = Profile.objects.create( + user=demo_user2, + professional_headline='UX Researcher | Growth Mindset', + transition_status='post-divorce', + bio='Rediscovering my love for hiking and photography. Seeking authentic connections and shared growth.', + location_city='Austin, TX', + ) + p2.intents.add(Intent.objects.get(name='Friendship')) + + # Social Feed Logic + hidden_post_ids = [] + if request.user.is_authenticated: + hidden_post_ids = HiddenPost.objects.filter(user=request.user).values_list('post_id', flat=True) + + posts = Post.objects.exclude(id__in=hidden_post_ids).select_related('author', 'author__profile').prefetch_related('comments', 'comments__author', 'reactions') + + # Filtering by intent (for discovery) + intent_filter = request.GET.get('intent') + if intent_filter: + profiles = Profile.objects.filter(intents__name__iexact=intent_filter) + else: + profiles = Profile.objects.all() + + intents = Intent.objects.all() + + # Dashboard logic for logged-in users + stats = {} + suggested_members = [] + suggested_events = [] + following_ids = [] + if request.user.is_authenticated: + # Quick Stats + stats = { + 'unread_messages': Message.objects.filter(recipient=request.user, is_read=False).count(), + 'pending_connections': 0, # Placeholder until connection request system exists + 'upcoming_events_count': request.user.attending_events.filter(start_time__gt=timezone.now()).count(), + 'completion_percentage': request.user.profile.profile_completion_percentage, + 'streak': request.user.profile.accountability_streak, + 'followers_count': request.user.profile.followers_count, + 'following_count': request.user.profile.following_count, + } + + following_ids = list(Follow.objects.filter(follower=request.user).values_list('followed_id', flat=True)) + + # Suggestions: Aligned members (shared intents or values) + user_intents = request.user.profile.intents.all() + user_values = request.user.profile.value_tags.all() + suggested_members = Profile.objects.filter( + Q(intents__in=user_intents) | Q(value_tags__in=user_values) + ).exclude(user=request.user).distinct()[:10] + + # Suggested Events: Upcoming events + from .models import Event + suggested_events = Event.objects.filter(start_time__gt=timezone.now()).order_by('start_time')[:5] context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "project_name": "CommonGround", + "profiles": profiles, + "intents": intents, + "current_intent": intent_filter, + "current_time": timezone.now(), + "posts": posts, + "stats": stats, + "suggested_members": suggested_members, + "suggested_events": suggested_events, + "post_types": Post.POST_TYPE_CHOICES, + "following_ids": following_ids, } return render(request, "core/index.html", context) + +@login_required +def create_post(request): + if request.method == 'POST': + content = request.POST.get('content') + image = request.FILES.get('image') + post_type = request.POST.get('post_type', 'reflection') + if content or image: + Post.objects.create(author=request.user, content=content, image=image, post_type=post_type) + return redirect('home') + +@login_required +def delete_post(request, post_id): + post = get_object_or_404(Post, id=post_id, author=request.user) + post.delete() + return redirect(request.META.get('HTTP_REFERER', 'home')) + +@login_required +def add_comment(request, post_id): + if request.method == 'POST': + post = get_object_or_404(Post, id=post_id) + content = request.POST.get('content') + if content: + Comment.objects.create(post=post, author=request.user, content=content) + return redirect(request.META.get('HTTP_REFERER', 'home')) + +@login_required +def toggle_reaction(request, post_id): + post = get_object_or_404(Post, id=post_id) + reaction_type = request.GET.get('type', 'heart') + reaction, created = Reaction.objects.get_or_create( + post=post, user=request.user, reaction_type=reaction_type + ) + if not created: + reaction.delete() + return redirect(request.META.get('HTTP_REFERER', 'home')) + +@login_required +def toggle_follow(request, username): + target_user = get_object_or_404(User, username=username) + if target_user == request.user: + return redirect(request.META.get('HTTP_REFERER', 'home')) + + follow, created = Follow.objects.get_or_create(follower=request.user, followed=target_user) + if not created: + follow.delete() + + return redirect(request.META.get('HTTP_REFERER', 'home')) + +@login_required +def hide_post(request, post_id): + post = get_object_or_404(Post, id=post_id) + HiddenPost.objects.get_or_create(user=request.user, post=post) + return redirect('home') + +def about(request): + return render(request, "core/about.html") + +def signup(request): + if request.method == 'POST': + form = UserCreationForm(request.POST) + if form.is_valid(): + user = form.save() + # Create profile + Profile.objects.create(user=user) + login(request, user) + return redirect('onboarding') + else: + form = UserCreationForm() + return render(request, 'registration/signup.html', {'form': form}) + +@login_required +def onboarding(request): + # Simplified onboarding for MVP + profile = request.user.profile + if request.method == 'POST': + profile.professional_headline = request.POST.get('headline', '') + profile.bio = request.POST.get('bio', '') + profile.onboarding_completed = True + profile.save() + return redirect('home') + return render(request, 'core/onboarding.html', {'profile': profile}) + +@login_required +def settings_view(request): + profile = request.user.profile + if request.method == 'POST': + profile.two_factor_enabled = 'two_factor' in request.POST + profile.save() + # In a real app we'd save more settings here + return redirect('settings') + return render(request, 'core/settings.html', {'profile': profile}) + +def get_started(request): + if not request.user.is_authenticated: + return redirect('signup') + if not request.user.profile.onboarding_completed: + return redirect('onboarding') + return redirect('home') + +@login_required +def inbox(request): + # Get all users the current user has messaged or received messages from + sent_to = Message.objects.filter(sender=request.user).values_list('recipient', flat=True) + received_from = Message.objects.filter(recipient=request.user).values_list('sender', flat=True) + + partner_ids = set(list(sent_to) + list(received_from)) + partners = User.objects.filter(id__in=partner_ids).select_related('profile') + + # Add last message to each partner for display + for partner in partners: + last_message = Message.objects.filter( + Q(sender=request.user, recipient=partner) | + Q(sender=partner, recipient=request.user) + ).order_by('-timestamp').first() + partner.last_message = last_message + + return render(request, 'core/inbox.html', {'partners': partners}) + +@login_required +def chat_detail(request, username): + partner = get_object_or_404(User, username=username) + if partner == request.user: + return redirect('inbox') + + if request.method == 'POST': + body = request.POST.get('body') + if body: + Message.objects.create(sender=request.user, recipient=partner, body=body) + return redirect('chat_detail', username=username) + + messages = Message.objects.filter( + Q(sender=request.user, recipient=partner) | + Q(sender=partner, recipient=request.user) + ).order_by('timestamp') + + # Mark as read + messages.filter(recipient=request.user, is_read=False).update(is_read=True) + + return render(request, 'core/chat.html', { + 'partner': partner, + 'chat_messages': messages + }) + +@login_required +def profile_view(request): + """Redirect to the current user's profile detail page.""" + return redirect('profile_detail', username=request.user.username) + +def profile_detail(request, username): + """View a user's profile.""" + target_user = get_object_or_404(User, username=username) + is_following = False + if request.user.is_authenticated: + is_following = Follow.objects.filter(follower=request.user, followed=target_user).exists() + return render(request, 'core/profile_detail.html', { + 'target_user': target_user, + 'is_following': is_following + }) + +@login_required +def edit_profile(request): + """Edit the current user's profile.""" + profile = request.user.profile + if request.method == 'POST': + profile.professional_headline = request.POST.get('headline', '') + profile.bio = request.POST.get('bio', '') + profile.location_city = request.POST.get('location', '') + + if 'avatar' in request.FILES: + profile.avatar = request.FILES['avatar'] + + profile.save() + return redirect('my_profile') + + return render(request, 'core/edit_profile.html', {'profile': profile}) diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..7bda0c8 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,281 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=Playfair+Display:wght@700&display=swap'); + +:root { + --cg-sand: #F4F1EA; + --cg-slate: #2C3E50; + --cg-blue: #5D9CEC; + --cg-border: #E0D8C3; + --cg-white: #FFFFFF; +} + +body { + font-family: 'Inter', sans-serif; + background-color: var(--cg-sand); + color: var(--cg-slate); +} + +h1, h2, h3, h4, .brand-font { + font-family: 'Playfair Display', serif; +} + +.navbar { + background-color: var(--cg-white); + border-bottom: 1px solid var(--cg-border); + padding: 1rem 0; +} + +.hero-section { + padding: 100px 0 60px; + background: linear-gradient(180deg, var(--cg-white) 0%, var(--cg-sand) 100%); + text-align: center; +} + +.profile-card { + background: var(--cg-white); + border: 1px solid var(--cg-border); + border-radius: 12px; + transition: transform 0.2s, box-shadow 0.2s; + overflow: hidden; + height: 100%; +} + +.profile-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0,0,0,0.05); +} + +.profile-avatar { + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + margin-bottom: 1rem; + border: 3px solid var(--cg-sand); +} + +.intent-badge { + background-color: rgba(93, 156, 236, 0.1); + color: var(--cg-blue); + border-radius: 20px; + padding: 4px 12px; + font-size: 0.85rem; + font-weight: 500; +} + +.transition-tag { + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: #888; + margin-bottom: 0.5rem; + display: block; +} + +.btn-primary-cg { + background-color: var(--cg-slate); + color: white; + border-radius: 8px; + padding: 10px 24px; + font-weight: 500; + border: none; +} + +.btn-primary-cg:hover { + background-color: #1a252f; + color: white; +} + +.btn-outline-cg { + border: 1px solid var(--cg-slate); + color: var(--cg-slate); + border-radius: 8px; + padding: 10px 24px; + font-weight: 500; +} + +.btn-outline-cg:hover { + background-color: var(--cg-slate); + color: white; +} + +.bg-light-cg { + background-color: #fcfbf8; +} + +.bg-sand { + background-color: var(--cg-sand); +} + +.filter-pill { + border: 1px solid var(--cg-border); + background: var(--cg-white); + padding: 8px 20px; + border-radius: 25px; + margin: 5px; + cursor: pointer; + transition: all 0.2s; +} + +.filter-pill.active { + background: var(--cg-slate); + color: white; + border-color: var(--cg-slate); +} + +.bottom-nav { + position: fixed; + bottom: 0; + width: 100%; + background: white; + border-top: 1px solid var(--cg-border); + display: flex; + justify-content: space-around; + padding: 10px 0; + z-index: 1000; +} + +.bottom-nav-item { + text-align: center; + color: var(--cg-slate); + text-decoration: none; + font-size: 0.75rem; +} + +.bottom-nav-item i { + font-size: 1.25rem; + display: block; +} + +main { + padding-bottom: 80px; /* Space for bottom nav */ +} + +/* Social Feed Styles */ +.nav-pills .nav-link.active { + background-color: var(--cg-slate); +} + +.nav-pills .nav-link { + color: var(--cg-slate); +} + +.upload-btn-wrapper { + position: relative; + overflow: hidden; + display: inline-block; +} + +.upload-btn-wrapper input[type=file] { + font-size: 100px; + position: absolute; + left: 0; + top: 0; + opacity: 0; + cursor: pointer; +} + +.extra-small { + font-size: 0.7rem; +} + +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Dashboard Layout */ +.dashboard-container { + padding-top: 1rem; +} + +.sticky-sidebar { + position: sticky; + top: 5.5rem; + height: calc(100vh - 7rem); + overflow-y: auto; +} + +.sidebar-nav .nav-link { + color: var(--cg-slate); + padding: 0.75rem 1rem; + border-radius: 8px; + margin-bottom: 0.25rem; + display: flex; + align-items: center; + gap: 0.75rem; + font-weight: 500; + transition: all 0.2s; +} + +.sidebar-nav .nav-link:hover { + background-color: rgba(44, 62, 80, 0.05); +} + +.sidebar-nav .nav-link.active { + background-color: var(--cg-slate); + color: white; +} + +/* Post Type Badges */ +.post-badge { + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + padding: 3px 8px; + border-radius: 4px; + margin-bottom: 0.5rem; + display: inline-block; + letter-spacing: 0.05em; +} + +.badge-reflection { background-color: #E1F5FE; color: #01579B; } +.badge-looking_for { background-color: #FFF3E0; color: #E65100; } +.badge-offering { background-color: #E8F5E9; color: #1B5E20; } +.badge-event_invite { background-color: #F3E5F5; color: #4A148C; } +.badge-progress_update { background-color: #E0F2F1; color: #004D40; } +.badge-skill_share { background-color: #FCE4EC; color: #880E4F; } + +/* Horizontal Scroll */ +.horizontal-scroll { + display: flex; + overflow-x: auto; + gap: 1rem; + padding-bottom: 1rem; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.horizontal-scroll::-webkit-scrollbar { + display: none; +} + +.suggestion-card { + min-width: 180px; + max-width: 180px; + background: white; + border-radius: 12px; + padding: 1rem; + border: 1px solid var(--cg-border); + text-align: center; +} + +.stat-widget { + background: white; + border-radius: 12px; + border: 1px solid var(--cg-border); + padding: 1.25rem; +} + +.stat-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid #f0f0f0; +} + +.stat-item:last-child { + border-bottom: none; }