From c452408106e4d44cc0fd2c3aaf7d3dbcb3e6e928 Mon Sep 17 00:00:00 2001 From: Dan Helfman Date: Mon, 24 Mar 2008 22:33:00 +0000 Subject: [PATCH] Finished remake of signup page. You can now click "signup" for non-free accounts even if you're not logged in. --- controller/Root.py | 9 +- controller/Users.py | 45 +++++++++- controller/Validate.py | 4 +- controller/test/Test_root.py | 15 ++++ controller/test/Test_users.py | 120 +++++++++++++++++++++++++++ static/css/product.css | 1 + static/images/sign_up_button.png | Bin 2051 -> 2071 bytes static/images/sign_up_button.xcf | Bin 3891 -> 4151 bytes static/images/subscribe_button.png | Bin 2097 -> 2101 bytes static/images/subscribe_button.xcf | Bin 3021 -> 3279 bytes static/images/unsubscribe_button.png | Bin 2129 -> 2137 bytes static/images/unsubscribe_button.xcf | Bin 3094 -> 3352 bytes static/js/Wiki.js | 9 +- view/Form_submit_page.py | 16 ++++ view/Front_page.py | 2 +- view/Main_page.py | 2 + view/Upgrade_page.py | 7 +- 17 files changed, 219 insertions(+), 11 deletions(-) create mode 100644 view/Form_submit_page.py diff --git a/controller/Root.py b/controller/Root.py index e1dd779..a723a14 100644 --- a/controller/Root.py +++ b/controller/Root.py @@ -56,9 +56,10 @@ class Root( object ): note_title = unicode, invite_id = Valid_id( none_okay = True ), after_login = Valid_string( min = 0, max = 100 ), + plan = Valid_int( none_okay = True ), user_id = Valid_id( none_okay = True ), ) - def default( self, note_title, invite_id = None, after_login = None, user_id = None ): + def default( self, note_title, invite_id = None, after_login = None, plan = None, user_id = None ): """ Convenience method for accessing a note in the main notebook by name rather than by note id. @@ -68,6 +69,8 @@ class Root( object ): @param invite_id: id of the invite used to get to this note (optional) @type after_login: unicode @param after_login: URL to redirect to after login (optional, must start with "/") + @type plan: int + @param plan: rate plan index (optional, defaults to None) @rtype: unicode @return: rendered HTML page """ @@ -81,6 +84,8 @@ class Root( object ): return dict( redirect = u"%s/%s?invite_id=%s" % ( https_url, note_title, invite_id ) ) if after_login: return dict( redirect = u"%s/%s?after_login=%s" % ( https_url, note_title, after_login ) ) + if plan: + return dict( redirect = u"%s/%s?plan=%s" % ( https_url, note_title, plan ) ) else: return dict( redirect = u"%s/%s" % ( https_url, note_title ) ) @@ -100,6 +105,8 @@ class Root( object ): result[ "invite_id" ] = invite_id if after_login and after_login.startswith( u"/" ): result[ "after_login" ] = after_login + if plan: + result[ "signup_plan" ] = plan return result diff --git a/controller/Users.py b/controller/Users.py index 8d74a6d..1f50b82 100644 --- a/controller/Users.py +++ b/controller/Users.py @@ -10,7 +10,7 @@ from model.Note import Note from model.Password_reset import Password_reset from model.Invite import Invite from Expose import expose -from Validate import validate, Valid_string, Valid_bool, Validation_error +from Validate import validate, Valid_string, Valid_bool, Valid_int, Validation_error from Database import Valid_id, end_transaction from Expire import strongly_expire from view.Json import Json @@ -21,6 +21,7 @@ from view.Blank_page import Blank_page from view.Thanks_note import Thanks_note from view.Thanks_error_note import Thanks_error_note from view.Processing_note import Processing_note +from view.Form_submit_page import Form_submit_page USERNAME_PATTERN = re.compile( "^[a-zA-Z0-9]+$" ) @@ -205,8 +206,9 @@ class Users( object ): email_address = ( Valid_string( min = 0, max = 60 ) ), signup_button = unicode, invite_id = Valid_id( none_okay = True ), + rate_plan = Valid_int( none_okay = True ), ) - def signup( self, username, password, password_repeat, email_address, signup_button, invite_id = None ): + def signup( self, username, password, password_repeat, email_address, signup_button, invite_id = None, rate_plan = None ): """ Create a new User based on the given information. Start that user with their own Notebook and a "welcome to your wiki" Note. For convenience, login the newly created user as well. @@ -223,6 +225,9 @@ class Users( object ): @param signup_button: ignored @type invite_id: unicode @param invite_id: id of invite to redeem upon signup (optional) + @type rate_plan: int + @param rate_plan: index of rate plan to signup for (optional). if greater than zero, redirect + to PayPal subscribe page after signup @rtype: json dict @return: { 'redirect': url, 'authenticated': userdict } @raise Signup_error: passwords don't match or the username is unavailable @@ -275,6 +280,9 @@ class Users( object ): self.convert_invite_to_access( invite, user_id ) redirect = u"/notebooks/%s" % invite.notebook_id + # if there's a requested rate plan, then redirect to the PayPal subscribe page + elif rate_plan and rate_plan > 0: + redirect = u"/users/subscribe?rate_plan=%s" % rate_plan # otherwise, just redirect to the newly created notebook else: redirect = u"/notebooks/%s" % notebook.object_id @@ -284,6 +292,39 @@ class Users( object ): authenticated = user, ) + @expose( view = Form_submit_page ) + @grab_user_id + @validate( + rate_plan = Valid_int(), + user_id = Valid_id(), + ) + def subscribe( self, rate_plan, user_id ): + """ + Submit a subscription form to PayPal, allowing the user to subscribe to the given rate plan. + + @type rate_plan: int + @param rate_plan: index of rate plan to subscribe to + @type user_id: unicode + @param user_id: id of current logged-in user + @rtype: dict + @return: { 'form': subscription_form_html } + @raise Signup_error: invalid rate plan, no logged-in user, or missing subscribe button + """ + if rate_plan == 0 or rate_plan >= len( self.__rate_plans ): + raise Signup_error( u"The rate plan is invalid." ) + + plan = self.__rate_plans[ rate_plan ] + button = plan.get( u"button" ) + if not button or not button.strip(): + raise Signup_error( + u"Sorry, that rate plan is not configured for subscriptions. Please contact %s." % \ + ( self.__support_email or u"support" ) + ) + + return dict( + form = button % user_id, + ) + @expose() @end_transaction @grab_user_id diff --git a/controller/Validate.py b/controller/Validate.py index f6a4794..3030f46 100644 --- a/controller/Validate.py +++ b/controller/Validate.py @@ -142,12 +142,14 @@ class Valid_int( object ): """ Validator for an integer value. """ - def __init__( self, min = None, max = None ): + def __init__( self, min = None, max = None, none_okay = False ): self.min = min self.max = max self.message = None + self.__none_okay = none_okay def __call__( self, value ): + if self.__none_okay and value in ( None, "None", "" ): return None value = int( value ) if self.min is not None and value < self.min: diff --git a/controller/test/Test_root.py b/controller/test/Test_root.py index c0a8c7e..533941e 100644 --- a/controller/test/Test_root.py +++ b/controller/test/Test_root.py @@ -238,6 +238,21 @@ class Test_root( Test_controller ): assert result.get( u"after_login" ) is None assert result[ u"user" ].object_id == self.anonymous.object_id + def test_default_with_plan( self ): + plan = u"17" + + result = self.http_get( + "/my_note?plan=%s" % plan, + ) + + assert result + assert result[ u"notes" ] + assert len( result[ u"notes" ] ) == 1 + assert result[ u"notes" ][ 0 ].object_id == self.anon_note.object_id + assert result[ u"notebook" ].object_id == self.anon_notebook.object_id + assert result[ u"signup_plan" ] == 17 + assert result[ u"user" ].object_id == self.anonymous.object_id + def test_default_after_login( self ): self.login() diff --git a/controller/test/Test_users.py b/controller/test/Test_users.py index 044ec17..c345fe3 100644 --- a/controller/test/Test_users.py +++ b/controller/test/Test_users.py @@ -93,6 +93,18 @@ class Test_users( Test_controller ): assert result[ u"redirect" ].startswith( u"/notebooks/" ) + def test_signup_with_rate_plan( self ): + result = self.http_post( "/users/signup", dict( + username = self.new_username, + password = self.new_password, + password_repeat = self.new_password, + email_address = self.new_email_address, + signup_button = u"sign up", + rate_plan = u"2", + ) ) + + assert result[ u"redirect" ] == u"/users/subscribe?rate_plan=2" + def test_signup_without_email_address( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, @@ -250,6 +262,62 @@ class Test_users( Test_controller ): assert rate_plan[ u"name" ] == u"super" assert rate_plan[ u"storage_quota_bytes" ] == 1337 * 10 + def test_current_after_signup_with_rate_plan( self ): + result = self.http_post( "/users/signup", dict( + username = self.new_username, + password = self.new_password, + password_repeat = self.new_password, + email_address = self.new_email_address, + signup_button = u"sign up", + rate_plan = u"2", + ) ) + session_id = result[ u"session_id" ] + + assert result[ u"redirect" ] == u"/users/subscribe?rate_plan=2" + + user = self.database.last_saved_obj + assert isinstance( user, User ) + result = cherrypy.root.users.current( user.object_id ) + + assert result[ u"user" ].object_id == user.object_id + assert result[ u"user" ].username == self.new_username + assert result[ u"user" ].email_address == self.new_email_address + + notebooks = result[ u"notebooks" ] + notebook = notebooks[ 0 ] + assert notebook.object_id + assert notebook.revision + assert notebook.name == u"my notebook" + assert notebook.trash_id + assert notebook.read_write == True + assert notebook.owner == True + assert notebook.rank == 0 + + notebook = notebooks[ 1 ] + assert notebook.object_id == notebooks[ 0 ].trash_id + assert notebook.revision + assert notebook.name == u"trash" + assert notebook.trash_id == None + assert notebook.read_write == True + assert notebook.owner == True + assert notebook.rank == None + + notebook = notebooks[ 2 ] + assert notebook.object_id == self.anon_notebook.object_id + assert notebook.revision == self.anon_notebook.revision + assert notebook.name == self.anon_notebook.name + assert notebook.trash_id == None + assert notebook.read_write == False + assert notebook.owner == False + assert notebook.rank == None + + assert result.get( u"login_url" ) is None + assert result[ u"logout_url" ] == self.settings[ u"global" ][ u"luminotes.https_url" ] + u"/users/logout" + + rate_plan = result[ u"rate_plan" ] + assert rate_plan[ u"name" ] == u"super" + assert rate_plan[ u"storage_quota_bytes" ] == 1337 * 10 + def test_signup_with_different_passwords( self ): result = self.http_post( "/users/signup", dict( username = self.new_username, @@ -261,6 +329,58 @@ class Test_users( Test_controller ): assert result[ u"error" ] + def test_subscribe( self ): + self.login() + + result = self.http_post( "/users/subscribe", dict( + rate_plan = u"1", + ), session_id = self.session_id ) + + form = result.get( u"form" ) + plan = self.settings[ u"global" ][ u"luminotes.rate_plans" ][ 1 ] + + assert form == plan[ u"button" ] % self.user.object_id + + def test_subscribe_with_free_rate_plan( self ): + self.login() + + result = self.http_post( "/users/subscribe", dict( + rate_plan = u"0", + ), session_id = self.session_id ) + + assert u"plan" in result[ u"error" ] + assert u"invalid" in result[ u"error" ] + + def test_subscribe_with_invalid_rate_plan( self ): + self.login() + + result = self.http_post( "/users/subscribe", dict( + rate_plan = u"17", + ), session_id = self.session_id ) + + assert u"plan" in result[ u"error" ] + assert u"invalid" in result[ u"error" ] + + def test_subscribe_without_login( self ): + result = self.http_post( "/users/subscribe", dict( + rate_plan = u"1", + ) ) + + assert u"user" in result[ u"error" ] + assert u"invalid" in result[ u"error" ] + + def test_subscribe_without_subscribe_button( self ): + self.login() + self.settings[ u"global" ][ u"luminotes.rate_plans" ][ 1 ][ u"button" ] = u" " + + result = self.http_post( "/users/subscribe", dict( + rate_plan = u"1", + ), session_id = self.session_id ) + + + print result + assert u"not configured" in result[ u"error" ] + def test_demo( self ): result = self.http_post( "/users/demo", dict() ) diff --git a/static/css/product.css b/static/css/product.css index 192e096..444937f 100644 --- a/static/css/product.css +++ b/static/css/product.css @@ -348,6 +348,7 @@ .upgrade_left_area { width: 400px; + margin-top: 1.5em; margin-bottom: 1em; } diff --git a/static/images/sign_up_button.png b/static/images/sign_up_button.png index c1c7250ba960a9b2a942450aa685273c076d4611..dfe55579956d4e600943444bc5b6a85a5ce7cb7e 100644 GIT binary patch delta 2010 zcmV<02POD}5SI{;Q-2s10s6 zA(nz{3jsn&nPE}_SX%0c_~?kzsxwv5s#A2d^+g?J9Cc`MMrQh$u^j{yM<}+$N2%{yW9+~>V}uYa6nH(7QAq2v#>znMGto^yWZcYn_L{$A&tEB(tqXflR; z&5e9ODLS>{;JC7M1u$Q8BNXURvKXA@Aj$vVNSOu%JD}$Xp@Hd~YkDpPn6J4JYJE_% z4(z3%*Z}ED&%}57`$`*Q>KRkc)Gkd>H&Gul$TzRC;!N_$&@a-5Y+O5+C38!8?d@*v*?;sG9$x3;u?-8dxHcK&n_1lL zYHR515Aus0Z?dX>I!z0wC*{0yqMbh;I?of|T#Bkl6SyW>)|Y_{4f19JJy%@P6xJnc z*A4n>Exp|TgRVawUKLs zrg`3E{eK@0o?-VPKTmF~1M?;&E<;ZOE&z*1Asz(d`rYTRZ}i(O3ye~Lg@uJla+ZvDbw;WvQ~-G8LB-*wD?EZw(k;MfhhB8@_%sIRVs^YtXo>fNLc5^qkY(|GJ9U_ zCKNMhn&;(dEws_IH119 zLVwpljJqE@$+Ec~R@ZrW^m#uno5DBlEe9>cn`Z|3%C^%utTG?3_j0B^#CM-L!$-;# zmd^HM1}vd*;yP$4w>RSnz_bwWTo~rz?X5ViGHV-(ICUY+BTuzq>QNqSDw|+OlD2+R z!wmNz0m8`#PQ!>FVuK?1Ya^hKPWU*KFn@pg&J1RjS$X8y3vAnWG1;b)(7q7^eUt-7 zyBLiad~H<$Uuw+duDQABaf6L3^FSZPV-;@n&uh zvanIyRsrglo4ow$^Cj%rRD~kL^M|_AWCh2UwM3mZ0@{$kCeV@=!!GOsZymTQ!GBr^ zl9C~ljHgl;?A=txJwNZ@hdaBd%r{xHz?IUzp@AME5*6jk13}QiWi`;_rdYHjAsM1w zLGZg107jzGtVu&MB8hmcjpH%my4Ygrq2xRyQ}B4KP+p+&UVkRJ>6>Z;!hH~IhyLTq zEtfjRmZ9&ccqTz5z>d`p7CpU~Uw`j8%No~FX!ihh(EA3o`nk*L;74*jn|GY!QcDNT zC#zXg+l$opvKYE27gz-SC!qBLbmjpIq3;;9Ud%YIE8qa`V4$-VTK6RDgMr&{s2*tj zE%Z5o#n68eTF(o^i!@-95bf6=^;5~_W+aiCf)M#OjozSDXgSRoEAt}Mm46KZaL8Z4 z*BANut!krzRg4UTuZ$Az1|poJo5EMuR+h)UIifa+lTv2tGT$Y3V+M12YKmS0b7sH}&4mzo?92Wglk-a-cokVf6 zn#hNdeX#bn~-A#mVxm#SwnGDj?+Kh{=@aD`4@keqosI z&}IJV&*Sg@eENbarU_52?cl!I!^9P_uwu6g77tdN08GuXCgzoywtuYdr~UVKHXp8H z^WiE0rsZmUb;(d-9>U^xwG^|fMa=bqx&WV=uYu&59AL34?r}prV~>$>171F3VdbnCy(1F7EnZg6it_CJ!^91U>fo@X&cG2J z>^awW4nN9SJXa_jGuiMQSXEd#Pa-g65{tu~x7fgLM*$pZ`O1EsbC7_V=~)_W zBDmS@4sM_6e1CUip>ALIl;Z+N?)#I~a|E!Wo?3B`xraZBgba?U0j)n2qH`e3?ceKTR)w8!-B-!_ zWko3R)PV)?p(=y@k9w&sw}B()#s!5sF+%~~T5&pIVbNlY_T$La%SEtM?Kky`diM|N ziD?FkETJe<3scfqvf5zrZQdJqLp=h>b>+CIA2c07*qoM6N<$f>liFNB{r; delta 1990 zcmV;%2RZnc5Q7kqQ-2f|I|gSM8#Mp`2aribK~z}7?U{RUR979xKj+??eJ0t>J0S@$ zCd5*ZCNRyMfT;52yXj+`0F>et+kj@9%Za?+Q}LYN@mM zkP;;BJ0+6JNkHWrP_h!-0?t7mW3BatDI*XcGsBQ*lDX5M~ zSX%M#)c1RBOsY?nLu$L2qHd}&q~U)xLrBMuG~?3kGfuHEg!zI%-^Z~CYKYEijTQk|4if4N`F${bo{TjhfMQK<|PCu8rX86x*2ER zb^tW1rOu+V7~B(_IdpYOIzVOYX)~1Ns;!St;sCn)1Z= zkykJA_%mnd^#>_0%x3$hC9GOf$}4a6a{G?Iaes3y4}Wf1mcg`1!{5qaZdYnU_h5iu z?tO#x4RdK)J~zSVrBj{!`RD~6`SxlwRZL-;WY}LCGIhvZ0Q8|H1{fhw^lJwFmDYal z{P91it;**Mx6J3At}!lkUuM;f9!fn5D;E{fu+R%ea8`ug)icQNpKB+_0ZmH_6ZL<3 z@hk_9`hR$Idp%e)6u3+y0k{M#7J*m*%xgEFzrW@K;3r>MNaNy(1!DyGNU_2*58ad~ z&ju-0M2b45C(U6PQs}A`0%N8krXew!04yXmOeh&5-jxxlqCf@UrBhur-r!7X8yJc5 z=pi4UT2?Hz3~2R@vFGq5di)WVR_BuK)Tk_S(0{zTjIofxvnK{{+7%8S>m?X9Xb!DW+>w>@~8 zTYr{#*--E02T%FPcBp*oj&jh0ym5AzukAU5%dYU5hC_-J4e^)n&SycHoqq?O>|oE~i-|Ukxb)2^7~>o{(ZhJydQs|IO@yin|~{OJiNAj z3f;XrRpf@z6qA$Po+NiO5U^3?j;0+m9M^eRV5PWX2 zKO^6a8m5LP1Lg#qcUpjT20Z-iXmO%^V{w?DuJ=nmS#mMuc`DevY+fFsH+Y$U`*QiG zFOPwMhGoHHo4WY)qETY1+*q+k1DhASLjtB{+2iX= z=j*FR;_HwazrVGZ{jGAXTkHLNzD@_>ogQG(P~0E3)LB-(j>w%q?UYXMsKUEL3Ld9L zbzux8K0Id1XYw^NX2J_+ZLC`urGHFNyQ`3O3nM&v=O{4~A_ll@6L;XS0nV&zyN6#G zRPhHD@?0iW1rzIN>wi^g++D!7#vq@q4O5hDQsFhPE7fC-`VjuCxQ1yiaF$FPuu>b0f*akzJFkp^?$WlY`!%QR55Gw3a1VX zBC@i1+BX7Yb$U=Ywo6&K(Jib;dS>RAt50OAKg`~isn*E5P-0OAKg R`~isn1C^)&F;G1N0{~}RD`x-z delta 132 zcmdn4uvu<`9V5p^duK-0eg+24Rg=3JRVVxLH%!)G;?cEbfB+vLB@Vv~cA!Fz~CRigPtHFz^~|uIAdp0sx?+5^Vqg diff --git a/static/images/subscribe_button.png b/static/images/subscribe_button.png index 8b325d91a2d5a97c55c4a36e844090dc49aa50c3..bd548506256d8e431c1faf42c3da8cd058ec6d62 100644 GIT binary patch delta 2013 zcmV<32O{{f5Va7HQw$gu0|3)9y+Z&12f~qGAAcC*@Tf^34R(XWvq^jEoVwbq_*P2F?C^xu;QcTkRF4~WssZ$ zf`9n=W=d7S-v(VZ1a3~}>Z;$55LSG&9Oe{5!4qJ~0x1a)hBWo^`1?>Bqne6$?$+>o3LvEaR5 z*{aH!iyh>-Q^|3ser6tYy~9hUGa0i{y7zd=IbbWG_-HxO&h^uFGQC7!T?!~sP$Hnj z^zumkNOdFIe|?HQ2S4Fh%{8(eCZ@a0dY{-hUF}}m&`Cw*AzU^CrG?Howr|(b?ti1( z$M+w5RoAxg@Yc6cVqt7nk&4Pc_-BtlO&(>%P6m8Ye*UK> z$`@u(w$M2|%V+9)*!*%Ifw0V)r7pf%kqf}SV{QEPTsNptR^sIOwbKyu1p8jj-ok4{MgVNluay;t7I4dtZpE zLmsZQ`pD0=vVBuNnU16c8C&;0Rnao$5Mipn9U*!P1mo?QMwh3S+xguQ3%d_{$aRQ3 z`$!f5;XsJvpY$@#AyGEZ!heNpUS9vp4YF+_W%Df%(|{V=e5_mIMphQtD4Ll<^@U!ZUzI^rR(brTIxe;Z*u2tBAgoYxu?OT3FT8dMlR;qNtQ7ja zMu-Nu-0bDG_ghG}LV2+xA>O8kTx{4;ODLl7jW1`h`@L3fboqJbhkx_O#gTFEuSQXW zT?oS~?B*r7<$)v@2vXdXYn9;8wwXM(tBDu)H4z8~d0~wUvmW5q`H7*g`U?9_-G>&RrKz`H-lX8kK|>^RUto2!g=bD}Mt3locAsBnmaA*^-Ek zc`l5`kV6Ua&NmGL(A?=~-|-Mlok0{8qG2BflQeFVt3a>^{H@UY@o;DviSnupJ!iFR z0?7pSue0*Sx0dnCcN+NB>PtW=DgN_Ne+^n3z#{1SH`L#Nt`wjYdd@+8GpY*F=t@$A z0AJ`k!tt+J_<#E0Vz%#xi*>cUT2YU`b1smJwe3r{&epKB+JVK;eFmC(6XN+h zJwPr;YCKqsGH!E>9S;p)xprc*s6SjX+)dIbNDtshiqh>j6PC4aPNS&a*#^^VArO@) za^KQ2oFmBb&1c#8b_su|%VEo+7BZ}wn|lT<%yNtnrhgFsD?m~249}6I+1>r4gmV*_6Pal@r~IcCkK zAxTRPiW$F>!p>8(0eI&AKK|R|A|?yur-w*Q()T$gGhdqJr|LBPtfqjO&PF6r#TSuK zM16dsQGXw=PMD}+AQ>mb3&upMPQ$M1IsEA1>x5$>*9XjOnA<=33X?*k3A2F7J|SVP zejB?!8VSJj+#z0G-A|6)%2Nvm*ni&6gKw0OYm2ZrCqRbH2qrr&yMa`*OjUI*Rn@s9 z8x^Y7wX<}V#7|FV@r!@D^r)dyxxAOz*+~?ruzz(~AHS(dXUX3C0eEF~2PMM2 zq|-Sla^t3m&7x9}AtR3J-Z3aL7!oMRkdelj3CEzRS3*_=3Nm6O8OQ4N_(byTG7jrl z-+$`!3taD)aHS~ZITU0CA~INvW9`d|c9{&5;{kNv5@;Wk$V^qpPE+n+@jg0pDq3~VC8`sxt6qnb+!F&`vPn<8eSZ{n^Rz>J}`;zJhvy$AsjtW%`Ll|Vac zpr21GF6k>Q>_%z*7%~45EtncCQ|_rM+D2wI;zemYhZxGf{Y+oA_Cwliu+Py>8&N;6 v4yYaJ3^c&tRfItL@Xruo>aWrN_t)ru_Vnm#ry1Hq00000NkvXXu0mjfhAZyQ delta 2009 zcmV;~2PXKn5U~)DQw$UqB@9!H&GY~O2fmSCAAb;V4Adk*gK0u|HmOiZ6I7*X6CO<~ ze`t!RR6(kyM6K!;(X?@slzylXB@HEywg?bWNo(^6Db#?$F@E9q3$O9McjorPENkz^ zYg0r%#YY;=+`0Fh|DFH6=bStDN+gJ=DiushOr$O6rMPk&QJE3QUIM9kAjw~Bri2FG zc7N!u!FO{isV}@Q#RwBsrGi=Wq3B7lkE0QkG1;Sm0-^;oxkBpG6rg&V;(6GT)&iW(9k`Ez2s(dF1kMRg`> zpG<$u(I3@zEc+9ELo%Gvwj>E;1DMjG=zmEp-QNSiAgW3Q1#U>qhlu~)&uk$$bGefO zcRG3Q^v}(MS~@*sJ5wFs3K3d(t z&flJ5-=R-9R&$+Phl!~!bF@t)L07+DtnXq~#bI1F17*d|F>HTUN57A*Pi!B5RexV> z<>BpbqeX()tTL-A{))?Hq^vkIpIEtSO*;zLUY>Fu(XqBP$nVQNF+# z8|5=~y=;D^A74;q&0-hdUX>5P{$uU@{agZZm5Zxk*h?lHy5{L`Q#s%EQfEYa6C8 z*UHXKg=9NYl4R_t_X$nUn70WN-VUVjEs%_#%`{PYda0d19JR3bNHh5kndctK0U+oL zaQu@#rZ^PJ=UBLO-NWmDyMIBhO{RRV1tL07L;Eo67CAZjUoSh}x`si7F`Hywdb5EQ z^K8tUo<{YhKDIrYMOYO)@p3JfTYYR^;l>x#sJYw=YJeACyMoCev0z3T10ExUeOztw z@Y?&WWLjb6d`D8eP0L+u*maRWNaI^y&0+6*ZQSVg^3IRuCd841?|&CzgufdpHp6aS zfm_Xx;sQw-Gv!(pIJ{#zkMC*ZrTvZg{C-|s}W_B#(;ms4OL%KJbznCgr@EQRa*<#_@JHowjmCm9wZVR#-NO`HWM14fxia^E&^ zo4gFLS!Fs0LOiv^!kZ^#zWe)D`UZVGztV;zNu0el2tawUF@G*mh=^`WAsqK!8jYa_ zlHy%x90H)J%gg@b0UEpfXad5)VGJfEVUeqWzZbl1(D!jHw2VY~p+fIj{TfHIfdlKT zeEF><{OX;0ezW=tP)3^f0@PiHRtHcD-T#KV8_=BwltJ$~sB1z92!~gYCi(bM|51*A z!@@U@%xC8TxPM%GkzcQ>!`n3*$j91t5WFLqP9)fhNt+#52t8+@u`el}x2qY*=V(nc z7Nd&W9AVe;K`hr#OceEFPlmfm8bz5t94TRXyk>%`{?BQY@9dww#bd!2R+#6$rDr&Y zpW~a)vhnQ_{#2XC*3wq8th$>w4_cVv7$Hn!?7sj_+MLFHv=Zt&gv(}rW5vItBc16J^b^AOZRfjx=llho*XnY zUM-E?r)C21>;wJ$r`JV9l_<;%ke(7<=a|fVWrml^)9~|}BBnbVP-MYyNI{dM{gaK+ z{zeItb$<*LTwoq?=VLtMjR>&L`EVQMs;z z#WNIsb~1-we&mWq4MD}yK4#{oP$Xdcl74Opbn<3BYp;2iS1GHZfr#eWpvIs#J)~FjB_!3Ac2*hGcHs zl(AU^MOiBHsOFtRGD880qAV39!Avj$jeQELkSNNEkYbG2>m8OUu&X$%@xImXm1r4I zaDSy~6gV_g4MHkdjPdqWO}|WriSYn>Zb@_uDP*T>@Y-{JT?0NPL2Ktwyxi<>H*N100000NkvXXu0mjf|Iy=v diff --git a/static/images/subscribe_button.xcf b/static/images/subscribe_button.xcf index af0bb3e75fd5fa19cc9e83a423b0579d33a61cbf..304dc5a66486b5e83133878010344a15fa11f07e 100644 GIT binary patch delta 265 zcmX>reqM5d9V5p^duK+@$qWo^4h#(3CnvWvs&3xG_?A(15d#ER0x5nVW?^7p-i9j1 zd~EYEW;JHrw@_gpAT172%FYTjZ7-@gyCeex=dsO_>|0oXEC!frMj)HZFST63C$TcM zXz~iijLApXWHLFRnt>EgIg}040(1*cKM)^A73WzD6x$3Et7koM0f-*}@dqIOU(YCV U0f-*}@dqIO4^*NC#M~gY0Q7e)C;$Ke delta 130 zcmX>vc~*Ra9V7cjduK-0eg+0Mm&x6Xsw_+l3=ESK*b+ALFim7sT?XWVfF%&~12GE& z1M@DB01!aMm``m!#H_}w`w=3_-~*(^ff%Tios)rq`yi?~yDS3(=c&!2>|0m>g=-Rb diff --git a/static/images/unsubscribe_button.png b/static/images/unsubscribe_button.png index 2e9fa5443062bb1e3678d3e4b2731469433c2ba8..5e0bbb4b9835d200f02e58d00bae0102fd310b19 100644 GIT binary patch delta 2059 zcmV+m2=w>S5ZMrrQw$gu10n`e{HFi_2j-Dq6@P4!O*X$sLL>=bC9woSS`nE@v9<$t z+6m)W>>vJY#abOY<7mgKql0y9r=`-42r5HmC^{AGj6=juVPF~x6bU~$-O) z^?#Ldht)SXLVf~@R)VJt6!rhxQ^JIB9|T&63{}wiz@7<*)i*anZ9P;sfx8%NE`AHPS6oRogL`^pTmnRG~lFJ_r>Iy}96m6F14SGl%+G<^V8|4jema1v+3|>o5OT=Qn3^TR697cp}Glo;M)M$ z>zf;)+z+04kkIB9t#r*(2HtAwn;R{Ld22x*1J!W{JxS9~caIrTm>K1_hBaQ^-^!OC zTtscTcbarl{?mnLlFmpd%HBhl*|4e#r(NOY{bwyZxoPxfuwm+uQv(FXQ&LEFsed{E zCobNk>G^lr_MK0%{{9M?&W6ddZ@kUMHC257OLccGc9l-)=q#F&;~WHbbcu#{;!J%OjK;Iyk~aetG~EzM)k_ZD%kJ3IyJ#jh;j=li>P=|CTz43%)qfP*n4D<{A>p?-Q?g;mau4E zrsXpop#@Nq3>#{Lu`X~Gf?~5+1(;Dtge)DylEqO?js9SqEgSqi`+tQZHoeltTP?Tv z^;e7e)6oIOG=r93)KXF4BpfptiAbjo+#Z93n~-KjON1r6fnPmQ#^bB=`R?!gc;iTr z_bvoD_^;6^Snsz5c6!6Ewv*AghF9fR?C6?V9#|%$3pnrablX(b=eX!7^aop zMkGmfuggF+HP$c90HCdVG%eQOPu&LKn~fe+Q==qX$E8M5g@44UE4Q(*$Rx`iLrq;; zX0UVQTo8bUN|l?z5ZP{(z>o%^ZV0qU-&j7GC|rdBH&4LL6S(?wfaQdGj={wqxn6~w zz^8E!y${_Zz%uCjCtM7m1{VNTh)aLqp-i2Z*Ojv6&>~hpcZoM2Jwwb;faPQbkHEzn zaJvSmg!Y{@1#7UU0w}@XzY_*hHnQH_NjL!X_<(z% zw;9~}w03RLuYsozF6O77>68q!o3n06SAre@YJB0eSbr1T17lQka+vFmgRyH1zTP!?fHv42KP&BLFk;AT0^>qpU z)-lQpM=N;YXa!E2$sfOXo!XM{1XtslRaX6`L5O@EC1?JWBb|f+mv7mK#1+c(3<|SE zQh%N>!DW>y;~S_$5{bSMl~GM$zR$qzn0T-49#T;isK}d4`$!zRgEpLYsLD4`Q-+Za z3!r~QTr=4L=o?lU))dO~OmbXe(ov5X?LrAUEQk3WD840g zXJ9j&jfvamPzH6Gl1d^3p3*tT%yhaIXmkNGu(z&zhq}W{P*5Rr&S_~|q#qFpa&}#t z#juzkv-cG0g0*tNw7cihQ<$W67v86002ovPDHLkV1glK^o0Nb delta 2051 zcmV+e2>kci5YZ5jQw$UqC=HP%gD(I82i}oi6@Tm|n{0lOgh&#=3Rr?5t%!_b)po#6 zJ7FA){X;EMu~vu9INGu5=wKb&X{odWf|hAzC^{AGjDzA#%fK|0U-Fwk_>qKU$!=hi zYw_k7R2-*e8r=iR3;rirbsg6WoR%DUsqsQXbw zm49WRaw&LfK~X>2ObHX>eGqCVF<6i1iFcGNVPb2mpk)CxwSl_|Y_1$Gg>^ml|MoL- zQ0?F>hNd>$q3;1;7h78ebpi0q zfQ&Z1Xyt33TJSa@wiXK+Vcr(dM?iIq2~YCe=X=H^Da@qun?o8e?K#CazC4GPI`0JO zru?r9&nTVYSdv})FSB}CBTl=*OM6aRc6@pCCb41aP|^&9uxAxA8mKw|M=lJ|_J7>F zZ2tbIS#^KCOlQMn(Ykk7v!an7eSQ92b6&@_5U>69EQ@FRXsLGI)xdc7XgAMpJ4}g7 z&`?$M;q922tTQBGMhc8LnDRD}h%usN7>>pu6PGegrzaGbY+JIs(QG0mU1lWjDlWjv zdwu+2<08J$TF&XK!yn#`$ViUv5Pzo02vbx*$xZ?z1(^gO5PC)ix=3dYCesF|u8!a? zQ29hdF{&!VIvUS#>RJ?!%f^Dn5>yoqcMP$x$xDGlC1aTMN7B^z3s8(C0I8(L`=@*H zxNOXCEJjiCW7%v{-gh%eXE07>CJPA|on70WN@gRym0*dUUm}!f!H0YK$EW$8!4xPHe`d2&27=rMy zOyfuHD`(q=7EWD@vHF+iFie3Eux42WYnD~;(9h2D+LO(+eWrpJ{?fy5-tMJq#}dfM zfo#}v4yRp3OPhRYVJUC?aDNVGy5r-pUijuLezm8Y7x(tzDNu>0O!ofd9t48*TRJgJ z7>Md@dc2Y58aIz_I?wKB=2GmoO=w3^;OXCAVb_5$WyKCsy2-wu&tuMvLW?sUp@mSg z5jJ=pM!LXN0gBCvD!@!aCT8gvmMo2GY6K%`Hm(lv%vUQ}`*Ig=w}0Q{ci*bw&jxzcm6GH!;ofmWa#f27dc^EsrfP;|G82EpvZ1_qA&$U$%8qQGaQ8;-mN&k?}(+^e9c7G|geh2Lx(%I>B0-W{Pc0)1)-1>#}8}GfXSJjYKxoy)FaQ z)L6B!0D$w|(Y#pyICcww?|j~aYHCy$>$ub;s*pJI>uk)fG=C|wr%-d3mPza!I1>S& zwLxVd5~J9y5*pMX)(xR{$&JOyox)WZFmMD0j^GNG0E>zB9D)lya=nTOI_! z$;T*D(&hwvVfI()vvW@%&fbfaoCpUnZG{{Um&-S(asyL!Sv5g{)UgU)VRzFpsSi8H9l{@bgsSmV_@X2~RN3P4=qpYL=oIadRCkg`L@UrWypDpd>Cu*mkT2x83B>lUK;7azef$mo*o_AtNW?E5Ki9o6ydx`5FFxDa!K)>v{fQ zJx-g+pT2gTmg@MOSWRnIS+%uQh%z0eWahXdos0pOZ`w$t73xY2DvCr_o-x5?l`6M4 zP=ALEB>IL`qME`?zk%Cv=e@RjP(@XszI1fhC(_UzvEj5sW0`@PGmLy#0Ks9Eftbq7 zG6Sz$j2qMKaf+OAO=bt6Z%AcGQ>ZI7DRGHWM?E6ig%WXCgasTZ{&~}9V3VAUiCbq- z!n#aJB@+Wr%``ESovwx2fdcHxy5oxZdTgCgP@!;|w7e}5L?j}p>w@wCQAq_bdyk>c zS}7MyyL&o4g(-{G^e|@MQPgBL5FCA`FA5{_+{f17`XU1C$Vo~kp#-XR5JqE zTz;wL3OU$cy2H-@baLF^Sl9y{{e~BvmUqr f#1DY@0}%hOXB4>r#1DY@0}%fQDp3PsZjf34?1n6E delta 133 zcmbOsHBDlI9V6#PduK-0eg+1%-pSpJ>XY?(Qa1B3RWj;MV1NK8AjJ>FEDQ|HD}c%x tQN@|}0L4-^Z(*)w*0q94`2cBgh!*xB1_mB3RB`q+1_mzS&2j8oSOBLa5$gZ| diff --git a/static/js/Wiki.js b/static/js/Wiki.js index 18d04e0..c6466a5 100644 --- a/static/js/Wiki.js +++ b/static/js/Wiki.js @@ -17,6 +17,7 @@ function Wiki( invoker ) { this.invites = evalJSON( getElement( "invites" ).value ); this.invite_id = getElement( "invite_id" ).value; this.after_login = getElement( "after_login" ).value; + this.signup_plan = getElement( "signup_plan" ).value; this.font_size = null; var total_notes_count_node = getElement( "total_notes_count" ); @@ -693,10 +694,12 @@ Wiki.prototype.create_editor = function ( id, note_text, deleted_from_id, revisi connect( editor, "invites_updated", function ( invites ) { self.invites = invites; self.share_notebook(); } ); connect( editor, "submit_form", function ( url, form, callback ) { var args = {} - if ( url == "/users/signup" || url == "/users/login" ) { + if ( url == "/users/signup" ) { args[ "invite_id" ] = self.invite_id; - if ( url == "/users/login" ) - args[ "after_login" ] = self.after_login; + args[ "rate_plan" ] = self.signup_plan; + } else if ( url == "/users/login" ) { + args[ "invite_id" ] = self.invite_id; + args[ "after_login" ] = self.after_login; } self.invoker.invoke( url, "POST", args, callback, form ); diff --git a/view/Form_submit_page.py b/view/Form_submit_page.py new file mode 100644 index 0000000..5819f3c --- /dev/null +++ b/view/Form_submit_page.py @@ -0,0 +1,16 @@ +from Tags import Html, Head, Body, Script + + +class Form_submit_page( Html ): + def __init__( self, form ): + Html.__init__( + self, + Head(), + Body( + form, + Script( # auto-submit the form + u"document.forms[ 0 ].submit();", + type = u"text/javascript", + ), + ), + ) diff --git a/view/Front_page.py b/view/Front_page.py index 6fc2c81..44b59bb 100644 --- a/view/Front_page.py +++ b/view/Front_page.py @@ -132,7 +132,7 @@ class Front_page( Product_page ): separator = u"", ), Div( - u"-Scott Tiner", + u"-Scott Tiner, Technical Writer", class_ = u"quote_signature" ), class_ = u"quote", diff --git a/view/Main_page.py b/view/Main_page.py index 351c5bf..bdd85d8 100644 --- a/view/Main_page.py +++ b/view/Main_page.py @@ -32,6 +32,7 @@ class Main_page( Page ): invites = None, invite_id = None, after_login = None, + signup_plan = None, ): startup_note_ids = [ startup_note.object_id for startup_note in startup_notes ] @@ -108,6 +109,7 @@ class Main_page( Page ): Input( type = u"hidden", name = u"invites", id = u"invites", value = json( invites ) ), Input( type = u"hidden", name = u"invite_id", id = u"invite_id", value = invite_id ), Input( type = u"hidden", name = u"after_login", id = u"after_login", value = after_login ), + Input( type = u"hidden", name = u"signup_plan", id = u"signup_plan", value = signup_plan ), Div( id = u"status_area", ), diff --git a/view/Upgrade_page.py b/view/Upgrade_page.py index 4cc2446..c29a178 100644 --- a/view/Upgrade_page.py +++ b/view/Upgrade_page.py @@ -98,8 +98,8 @@ class Upgrade_page( Product_page ): alt = u"More room to stretch out", ), Ul( - Li( u"More room for your wiki notes." ), - Li( u"More room for your documents and files." ), + Li( u"More space for your wiki notes." ), + Li( u"More space for your documents and files." ), class_ = u"upgrade_text", ), Img( @@ -176,7 +176,8 @@ class Upgrade_page( Product_page ): class_ = u"price_text", separator = u"", ), - user and user.username and user.rate_plan != index and plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id, + user and user.username not in ( u"anonymous", None ) and user.rate_plan != index \ + and plan.get( u"button" ).strip() and plan.get( u"button" ) % user.object_id or None, ) or None, ( not user or user.username in ( u"anonymous", None ) ) and Div( A(