Checkpoint

Commit
6e621009df351b8cfcdde08a3a3353ee6f2f7077
Author
Marius Peter <marius.peter@tutanota.com>
Author date
Committer
Marius Peter <marius.peter@tutanota.com>
Committer date
Changed files
src/MyFitnessTracker/MFTHeaderProspectComponent.class.st
index 8337d2ce..00000000 100644..000000
@@ -1,111 +0,0 @@
1 Removed: Class {
2 Removed: #name : #MFTHeaderProspectComponent,
3 Removed: #superclass : #SBSComponent,
4 Removed: #instVars : [
5 Removed: 'email',
6 Removed: 'localPart',
7 Removed: 'domain'
8 Removed: ],
9 Removed: #category : #'MyFitnessTracker-Components'
10 Removed: }
11 Removed:
12 Removed: { #category : #adding }
13 Removed: MFTHeaderProspectComponent >> addUserEmail [
14 Removed:
15 Removed: | emailToCheck emailPattern |
16 Removed: emailToCheck := self call: MFTEmailSignupDialog new.
17 Removed: emailPattern := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]{3,}\.[a-zA-Z]{2,}$'
18 Removed: asRegex.
19 Removed: (emailToCheck isNil or: [ (emailPattern matches: emailToCheck) not ])
20 Removed: ifTrue: [ ^ nil ].
21 Removed: email := emailToCheck.
22 Removed: MFTSQLite3Database storeProspectEmail: email
23 Removed: ]
24 Removed:
25 Removed: { #category : #rendering }
26 Removed: MFTHeaderProspectComponent >> renderContentOn: html [
27 Removed:
28 Removed: self session user isLoggedIn ifTrue: [ "If a user is logged in, the CTA banner is not displayed."
29 Removed: ^ nil ].
30 Removed: email
31 Removed: ifNil: [
32 Removed: html alert
33 Removed: beWarning;
34 Removed: beDismissible;
35 Removed: marginBottom: 0;
36 Removed: with: [
37 Removed: html closeButton dataDismiss: 'alert'.
38 Removed: html alertHeading: 'This app is still in beta.'.
39 Removed: html paragraph: [
40 Removed: html
41 Removed: text:
42 Removed: 'My Fitness Tracker is still under heavy development, with improvements being made daily.';
43 Removed: space;
44 Removed: text: 'I can''t wait to share the finished product with you!' ].
45 Removed: html form with: [
46 Removed: html label
47 Removed: style: 'margin-right: 1em';
48 Removed: with: [ html strong: 'Want to know when version 1.0 is out?' ].
49 Removed: html formButton
50 Removed: beInfo;
51 Removed: callback: [ self addUserEmail ];
52 Removed: with: 'E-mail me!' ] ] ]
53 Removed: ifNotNil: [
54 Removed: html alert
55 Removed: beSuccess;
56 Removed: beDismissible;
57 Removed: marginBottom: 0;
58 Removed: with: [
59 Removed: html closeButton dataDismiss: 'alert'.
60 Removed: html text: email , ' will be notified when v1 is released!' ] ]
61 Removed: ]
62 Removed:
63 Removed: { #category : #rendering }
64 Removed: MFTHeaderProspectComponent >> renderEmailInputModalButtonOn: html [
65 Removed:
66 Removed: ^ html formButton
67 Removed: bePrimary;
68 Removed: beSmall;
69 Removed: dataToggle: 'modal';
70 Removed: dataTarget: '#emailInputModal';
71 Removed: with: 'E-mail me!'
72 Removed: ]
73 Removed:
74 Removed: { #category : #rendering }
75 Removed: MFTHeaderProspectComponent >> renderEmailInputModalOn: html [
76 Removed:
77 Removed: html modal
78 Removed: id: 'emailInputModal';
79 Removed: fade;
80 Removed: with: [
81 Removed: html modalDialog
82 Removed: beCentered;
83 Removed: with: [
84 Removed: html modalContent: [
85 Removed: html form: [
86 Removed: html modalHeader: [
87 Removed: html modalTitle
88 Removed: level5;
89 Removed: with:
90 Removed: 'You''ll be the first to know when My Fitness Tracker is ready to use.'.
91 Removed: html modalCloseButton ].
92 Removed:
93 Removed: html modalBody: [
94 Removed: html inputGroup: [
95 Removed: html formTextInput
96 Removed: callback: [ :value | localPart := value ];
97 Removed: placeholder: 'my-user'.
98 Removed: html inputGroupAppend: [ html inputGroupText: '@' ].
99 Removed: html formTextInput
100 Removed: callback: [ :value | domain := value ];
101 Removed: placeholder: 'example.com' ] ].
102 Removed:
103 Removed: html modalFooter: [
104 Removed: html paragraph:
105 Removed: 'You will receive a single e-mail when the app is ready to use. Your e-mail address shall not be used in any other way.'.
106 Removed: html formButton
107 Removed: bePrimary;
108 Removed: beSubmit;
109 Removed: callback: [ email := localPart , '@' , domain ];
110 Removed: with: 'Confirm' ] ] ] ] ]
111 Removed: ]
src/MyFitnessTracker/MFTLoginRegisterLogoutComponent.class.st
index 5391f910..45f88a8e 100644..100644
@@ -25,11 +25,10 @@
25 25 passwordHash := existingUser
26 26 at: #password_hash
27 27 ifAbsent: [ String empty ].
28 Removed:
29 28 aMFTUser passwordHash = passwordHash
30 Removed: ifTrue: [ self login: (MFTUser newFromDictionary: existingUser) ]
31 Removed: ifFalse: [ "TODO: user should be prompted to retry logging in."
32 Removed: ^ nil ]
29 Added: ifTrue: [
30 Added: self session login: (MFTUser newFromDictionary: existingUser) ]
31 Added: ifFalse: [ self inform: 'Login failed' ]
33 32 ]
34 33
35 34 { #category : #initialization }
@@ -37,19 +36,6 @@
37 36
38 37 super initialize.
39 38 user := MFTUser new
40 Removed: ]
41 Removed:
42 Removed: { #category : #login }
43 Removed: MFTLoginRegisterLogoutComponent >> login: aMFTUser [
44 Removed:
45 Removed: | db |
46 Removed: db := MFTSQLite3Database newFromDefault.
47 Removed: db connection open.
48 Removed: self session db: db.
49 Removed: self session db updateLoginDateAndTime: aMFTUser.
50 Removed: self session user: aMFTUser.
51 Removed: self session user isLoggedIn: true.
52 Removed: self session mainPages at: 1 put: MFTUserLandingPage new
53 39 ]
54 40
55 41 { #category : #'as yet unclassified' }
src/MyFitnessTracker/MFTMainNavigationBarComponent.class.st
index 7d0a688f..1c15bc80 100644..100644
@@ -36,7 +36,11 @@
36 36 { #category : #rendering }
37 37 MFTMainNavigationBarComponent >> renderNavigationBarItemsOn: html [
38 38
39 Removed: html navigationBarBrand: 'MyFitnessTracker'.
39 Added: | pm |
40 Added: pm := self session pageManager.
41 Added: html navigationBarBrand
42 Added: callback: [ pm activePage: (pm mainPages at: 1) ];
43 Added: with: 'MyFitnessTracker'.
40 44 "The toggler that is only visible when reducing the width of screen"
41 45 html navigationBarToggler
42 46 beButtonType;
@@ -47,23 +51,13 @@
47 51 id: 'navbarCollapsed';
48 52 with: [
49 53 html navigationBarNavigation: [
50 Removed: self session mainPages do: [ :page |
54 Added: pm mainPages do: [ :page |
51 55 html navigationItem
52 Removed: beActiveIf: [ self session activePage = page ];
56 Added: beActiveIf: [ pm activePage = page ];
53 57 with: [
54 58 html navigationLink
55 Removed: callback: [ self session activePage: page ];
56 Removed: with: page title asCapitalizedPhrase ] ].
57 Removed: html navigationItem dropdown with: [
58 Removed: html navigationLink
59 Removed: dropdownToggle;
60 Removed: dataToggle: 'dropdown';
61 Removed: with: 'Learn'.
62 Removed: html dropdownMenu
63 Removed: attributeAt: 'aria-labelledby' put: 'navbarDropdown';
64 Removed: with: [
65 Removed: html dropdownItem: 'Exercises'.
66 Removed: html dropdownItem: 'Muscles' ] ] ].
59 Added: callback: [ pm activePage: page ];
60 Added: with: page title asCapitalizedPhrase ] ] ].
67 61 html div
68 62 class: 'ms-auto';
69 63 with: [ html render: loginRegisterLogout ] ]
src/MyFitnessTracker/MFTMuscle.class.st
index 80765dad..ad59b8d7 100644..100644
@@ -177,7 +177,7 @@
177 177
178 178 | muscleNameFromSender |
179 179 muscleNameFromSender := (thisContext sender selector splitCamelCase
180 Removed: collect: #asLowercase) joinUsing: ' '.
180 Added: collect: #asLowercase) joinUsing: Character space.
181 181 ^ self new
182 182 latinName: muscleNameFromSender;
183 183 yourself
src/MyFitnessTracker/MFTPageManager.class.st
index 00000000..4802804a 000000..100644
@@ -0,0 +1,103 @@
1 Added: Class {
2 Added: #name : #MFTPageManager,
3 Added: #superclass : #Object,
4 Added: #instVars : [
5 Added: 'mainNavigationBar',
6 Added: 'mainPages',
7 Added: 'activePage'
8 Added: ],
9 Added: #category : #MyFitnessTracker
10 Added: }
11 Added:
12 Added: { #category : #accessing }
13 Added: MFTPageManager >> activePage [
14 Added:
15 Added: ^ activePage
16 Added: ]
17 Added:
18 Added: { #category : #accessing }
19 Added: MFTPageManager >> activePage: aMFTComponent [
20 Added:
21 Added: activePage := aMFTComponent
22 Added: ]
23 Added:
24 Added: { #category : #'as yet unclassified' }
25 Added: MFTPageManager >> defaultFooter [
26 Added:
27 Added: ^ MFTFooterComponent new
28 Added: ]
29 Added:
30 Added: { #category : #'as yet unclassified' }
31 Added: MFTPageManager >> defaultPublicPages [
32 Added:
33 Added: ^ OrderedCollection newFromArray: {
34 Added: MFTPublicLandingPage new.
35 Added: MFTWorkoutsListPage placeholder.
36 Added: MFTWeightsListPage placeholder.
37 Added: MFTLearnPage new.
38 Added: MFTProfilePage new }
39 Added: ]
40 Added:
41 Added: { #category : #initialization }
42 Added: MFTPageManager >> initialize [
43 Added:
44 Added: mainNavigationBar := MFTMainNavigationBarComponent new.
45 Added: mainPages := self defaultPublicPages.
46 Added: activePage := mainPages first
47 Added: ]
48 Added:
49 Added: { #category : #'as yet unclassified' }
50 Added: MFTPageManager >> loadUserLandingPage [
51 Added:
52 Added: mainPages at: 1 put: MFTUserLandingPage new
53 Added: ]
54 Added:
55 Added: { #category : #'as yet unclassified' }
56 Added: MFTPageManager >> loadUserPages [
57 Added:
58 Added: self loadUserLandingPage.
59 Added: self loadUserWorkouts.
60 Added: self loadUserWeights.
61 Added: ]
62 Added:
63 Added: { #category : #'as yet unclassified' }
64 Added: MFTPageManager >> loadUserWeights [
65 Added:
66 Added: (mainPages at: 3) loadUserWeights
67 Added: ]
68 Added:
69 Added: { #category : #'as yet unclassified' }
70 Added: MFTPageManager >> loadUserWorkouts [
71 Added:
72 Added: (mainPages at: 2) loadUserWorkouts
73 Added: ]
74 Added:
75 Added: { #category : #accessing }
76 Added: MFTPageManager >> mainNavigationBar [
77 Added:
78 Added: ^ mainNavigationBar
79 Added: ]
80 Added:
81 Added: { #category : #accessing }
82 Added: MFTPageManager >> mainNavigationBar: anObject [
83 Added:
84 Added: mainNavigationBar := anObject
85 Added: ]
86 Added:
87 Added: { #category : #accessing }
88 Added: MFTPageManager >> mainPages [
89 Added:
90 Added: ^ mainPages
91 Added: ]
92 Added:
93 Added: { #category : #accessing }
94 Added: MFTPageManager >> mainPages: aCollection [
95 Added:
96 Added: mainPages := aCollection
97 Added: ]
98 Added:
99 Added: { #category : #'as yet unclassified' }
100 Added: MFTPageManager >> registerProspectHeader [
101 Added:
102 Added: ^ MFTRegisterProspectHaederComponent new
103 Added: ]
src/MyFitnessTracker/MFTPublicLandingPage.class.st
index 522b0157..ccf2c59a 100644..100644
@@ -16,6 +16,8 @@
16 16
17 17 html container: [
18 18 self renderMainCardOn: html.
19 Added: self renderWorkoutsGraphChartOn: html.
20 Added: self renderWeightsGraphChartOn: html.
19 21 self renderPromisesCardOn: html ]
20 22 ]
21 23
@@ -36,7 +38,8 @@
36 38 beLarge;
37 39 class: 'mt-3';
38 40 callback: [
39 Removed: self session activePage: (self session mainPages at: 2) ];
41 Added: self session pageManager activePage:
42 Added: (self session pageManager mainPages at: 2) ];
40 43 with: 'Log a workout' ] ] ]
41 44 ]
42 45
@@ -46,10 +49,100 @@
46 49 html card
47 50 marginTopAndBottom: 3;
48 51 with: [
49 Removed: html cardHeader: 'We promise you...'.
52 Added: html cardHeader: 'My promise to all account owners...'.
50 53 html cardBody: [
51 54 html unorderedList: [
52 55 html listItem: 'All features shall always be entirely free.'.
53 56 html listItem: 'No user data shall ever be sold.'.
54 57 html listItem: '.' ] ] ]
58 Added: ]
59 Added:
60 Added: { #category : #rendering }
61 Added: MFTPublicLandingPage >> renderWeightsGraphChartOn: html [
62 Added:
63 Added: html card with: [
64 Added: html cardHeader: 'Weight'.
65 Added: html cardBody: [ self renderWeightsGraphOn: html ] ]
66 Added: ]
67 Added:
68 Added: { #category : #rendering }
69 Added: MFTPublicLandingPage >> renderWeightsGraphOn: html [
70 Added:
71 Added: | labels data |
72 Added: labels := self session weights collect: #date.
73 Added: data := #( 10 15 40 25 ).
74 Added: html canvas
75 Added: id: 'weightChart';
76 Added: width: 100;
77 Added: height: 30.
78 Added: html script url: 'https://cdn.jsdelivr.net/npm/chart.js'.
79 Added: html script:
80 Added: 'var ctx = document.getElementById("weightChart").getContext("2d");
81 Added: var weightChart = new Chart(ctx, {
82 Added: type: "bar",
83 Added: data: {
84 Added: labels: ' , labels asJavascript , ',
85 Added: datasets: [{
86 Added: label: "# of Votes",
87 Added: data: ' , data asJavascript , ',
88 Added: backgroundColor: "rgba(255, 99, 132, 0.2)",
89 Added: borderColor: "rgba(255, 99, 132, 1)",
90 Added: borderWidth: 1
91 Added: }]
92 Added: },
93 Added: options: {
94 Added: scales: {
95 Added: yAxes: [{
96 Added: ticks: {
97 Added: beginAtZero: true
98 Added: }
99 Added: }]
100 Added: }
101 Added: }
102 Added: });'
103 Added: ]
104 Added:
105 Added: { #category : #rendering }
106 Added: MFTPublicLandingPage >> renderWorkoutsGraphChartOn: html [
107 Added:
108 Added: html card with: [
109 Added: html cardHeader: 'Workouts'.
110 Added: html cardBody: [ self renderWorkoutsGraphOn: html ] ]
111 Added: ]
112 Added:
113 Added: { #category : #rendering }
114 Added: MFTPublicLandingPage >> renderWorkoutsGraphOn: html [
115 Added:
116 Added: | labels data |
117 Added: labels := #( 'January' 'February' 'March' 'April' ).
118 Added: data := #( 10 15 40 25 ).
119 Added: html canvas
120 Added: id: 'workoutChart';
121 Added: width: 100;
122 Added: height: 30.
123 Added: html script url: 'https://cdn.jsdelivr.net/npm/chart.js'.
124 Added: html script:
125 Added: 'var ctx = document.getElementById("workoutChart").getContext("2d");
126 Added: var workoutChart = new Chart(ctx, {
127 Added: type: "bar",
128 Added: data: {
129 Added: labels: ' , labels asJavascript , ',
130 Added: datasets: [{
131 Added: label: "# of Votes",
132 Added: data: ' , data asJavascript , ',
133 Added: backgroundColor: "rgba(255, 99, 132, 0.2)",
134 Added: borderColor: "rgba(255, 99, 132, 1)",
135 Added: borderWidth: 1
136 Added: }]
137 Added: },
138 Added: options: {
139 Added: scales: {
140 Added: yAxes: [{
141 Added: ticks: {
142 Added: beginAtZero: true
143 Added: }
144 Added: }]
145 Added: }
146 Added: }
147 Added: });'
55 148 ]
src/MyFitnessTracker/MFTRegisterProspectHaederComponent.class.st
index 00000000..c97e0a69 000000..100644
@@ -0,0 +1,109 @@
1 Added: Class {
2 Added: #name : #MFTRegisterProspectHaederComponent,
3 Added: #superclass : #SBSComponent,
4 Added: #instVars : [
5 Added: 'email',
6 Added: 'localPart',
7 Added: 'domain'
8 Added: ],
9 Added: #category : #'MyFitnessTracker-Components'
10 Added: }
11 Added:
12 Added: { #category : #adding }
13 Added: MFTRegisterProspectHaederComponent >> addUserEmail [
14 Added:
15 Added: | emailToCheck emailPattern |
16 Added: emailToCheck := self call: MFTEmailSignupDialog new.
17 Added: emailPattern := '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]{3,}\.[a-zA-Z]{2,}$'
18 Added: asRegex.
19 Added: (emailToCheck isNil or: [ (emailPattern matches: emailToCheck) not ])
20 Added: ifTrue: [ ^ nil ].
21 Added: email := emailToCheck.
22 Added: MFTSQLite3Database storeProspectEmail: email
23 Added: ]
24 Added:
25 Added: { #category : #rendering }
26 Added: MFTRegisterProspectHaederComponent >> renderContentOn: html [
27 Added:
28 Added: email
29 Added: ifNil: [
30 Added: html alert
31 Added: beWarning;
32 Added: beDismissible;
33 Added: marginBottom: 0;
34 Added: with: [
35 Added: html closeButton dataDismiss: 'alert'.
36 Added: html alertHeading: 'This app is still in beta.'.
37 Added: html paragraph: [
38 Added: html
39 Added: text:
40 Added: 'My Fitness Tracker is still under heavy development, with improvements being made daily.';
41 Added: space;
42 Added: text: 'I can''t wait to share the finished product with you!' ].
43 Added: html form with: [
44 Added: html label
45 Added: style: 'margin-right: 1em';
46 Added: with: [ html strong: 'Want to know when version 1.0 is out?' ].
47 Added: html formButton
48 Added: beInfo;
49 Added: callback: [ self addUserEmail ];
50 Added: with: 'E-mail me!' ] ] ]
51 Added: ifNotNil: [
52 Added: html alert
53 Added: beSuccess;
54 Added: beDismissible;
55 Added: marginBottom: 0;
56 Added: with: [
57 Added: html closeButton dataDismiss: 'alert'.
58 Added: html text: email , ' will be notified when v1 is released!' ] ]
59 Added: ]
60 Added:
61 Added: { #category : #rendering }
62 Added: MFTRegisterProspectHaederComponent >> renderEmailInputModalButtonOn: html [
63 Added:
64 Added: ^ html formButton
65 Added: bePrimary;
66 Added: beSmall;
67 Added: dataToggle: 'modal';
68 Added: dataTarget: '#emailInputModal';
69 Added: with: 'E-mail me!'
70 Added: ]
71 Added:
72 Added: { #category : #rendering }
73 Added: MFTRegisterProspectHaederComponent >> renderEmailInputModalOn: html [
74 Added:
75 Added: html modal
76 Added: id: 'emailInputModal';
77 Added: fade;
78 Added: with: [
79 Added: html modalDialog
80 Added: beCentered;
81 Added: with: [
82 Added: html modalContent: [
83 Added: html form: [
84 Added: html modalHeader: [
85 Added: html modalTitle
86 Added: level5;
87 Added: with:
88 Added: 'You''ll be the first to know when My Fitness Tracker is ready to use.'.
89 Added: html modalCloseButton ].
90 Added:
91 Added: html modalBody: [
92 Added: html inputGroup: [
93 Added: html formTextInput
94 Added: callback: [ :value | localPart := value ];
95 Added: placeholder: 'my-user'.
96 Added: html inputGroupAppend: [ html inputGroupText: '@' ].
97 Added: html formTextInput
98 Added: callback: [ :value | domain := value ];
99 Added: placeholder: 'example.com' ] ].
100 Added:
101 Added: html modalFooter: [
102 Added: html paragraph:
103 Added: 'You will receive a single e-mail when the app is ready to use. Your e-mail address shall not be used in any other way.'.
104 Added: html formButton
105 Added: bePrimary;
106 Added: beSubmit;
107 Added: callback: [ email := localPart , '@' , domain ];
108 Added: with: 'Confirm' ] ] ] ] ]
109 Added: ]
src/MyFitnessTracker/MFTSQLite3Database.class.st
index 425fecaa..02fae232 100644..100644
@@ -8,6 +8,12 @@
8 8 }
9 9
10 10 { #category : #'as yet unclassified' }
11 Added: MFTSQLite3Database class >> dateTimeNow [
12 Added:
13 Added: ^ DateAndTime now asUTC rounded
14 Added: ]
15 Added:
16 Added: { #category : #'as yet unclassified' }
11 17 MFTSQLite3Database class >> defaultConnection [
12 18
13 19 ^ self newFromDefault connection
@@ -45,13 +51,30 @@
45 51 "date_of_birth" TEXT,
46 52 PRIMARY KEY("id" AUTOINCREMENT)
47 53 )';
54 Added: execute: 'CREATE TABLE IF NOT EXISTS "workouts" (
55 Added: "id" INTEGER UNIQUE,
56 Added: "username" TEXT NOT NULL,
57 Added: "password_hash" BLOB NOT NULL,
58 Added: "real_first_name" TEXT,
59 Added: "real_last_name" TEXT,
60 Added: "date_of_birth" TEXT,
61 Added: PRIMARY KEY("id" AUTOINCREMENT)
62 Added: )';
63 Added: execute: 'CREATE TABLE IF NOT EXISTS "weights" (
64 Added: "id" INTEGER UNIQUE,
65 Added: "user_id" INTEGER NOT NULL,
66 Added: "date" TEXT NOT NULL,
67 Added: "value" NUMERIC NOT NULL,
68 Added: PRIMARY KEY("id" AUTOINCREMENT)
69 Added: )';
48 70 close
49 71 ]
50 72
51 73 { #category : #'class initialization' }
52 74 MFTSQLite3Database class >> initialize [
53 75
54 Removed: defaultLocation := (Smalltalk imageDirectory / 'mft.sqlite3') fullName
76 Added: defaultLocation := (Smalltalk imageDirectory / 'mft.sqlite3') fullName.
77 Added: self ensureTablesExist.
55 78 ]
56 79
57 80 { #category : #'instance creation' }
@@ -60,6 +83,64 @@
60 83 ^ self on: defaultLocation
61 84 ]
62 85
86 Added: { #category : #'class initialization' }
87 Added: MFTSQLite3Database class >> populateDefaultWeights [
88 Added:
89 Added: self defaultConnection
90 Added: open;
91 Added: execute: 'INSERT INTO weights(user_id, date, value)
92 Added: VALUES( ?, ?, ?)'
93 Added: with: {
94 Added: 7.
95 Added: DateAndTime yesterday asUTC rounded.
96 Added: 90.5 };
97 Added: execute: 'INSERT INTO weights(user_id, date, value)
98 Added: VALUES( ?, ?, ?)'
99 Added: with: {
100 Added: 7.
101 Added: DateAndTime today asUTC rounded.
102 Added: 101.3 };
103 Added: execute: 'INSERT INTO weights(user_id, date, value)
104 Added: VALUES( ?, ?, ?)'
105 Added: with: {
106 Added: 7.
107 Added: DateAndTime now asUTC rounded.
108 Added: 90.5 };
109 Added: close
110 Added: ]
111 Added:
112 Added: { #category : #'class initialization' }
113 Added: MFTSQLite3Database class >> populateDefaultWorkouts [
114 Added:
115 Added: self defaultConnection
116 Added: open;
117 Added: execute:
118 Added: 'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
119 Added: VALUES( ?, ?, ?, ?)'
120 Added: with: {
121 Added: 7.
122 Added: DateAndTime yesterday asUTC rounded.
123 Added: MFTMuscle quadricepsFemoris latinName.
124 Added: MFTMuscle hamstring englishName };
125 Added: execute:
126 Added: 'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
127 Added: VALUES( ?, ?, ?, ?)'
128 Added: with: {
129 Added: 7.
130 Added: self dateTimeNow.
131 Added: MFTMuscle latissimusDorsi latinName.
132 Added: MFTMuscle erectorSpinae latinName };
133 Added: execute:
134 Added: 'INSERT INTO workouts(user_id, date, primary_muscle, secondary_muscles)
135 Added: VALUES( ?, ?, ?, ?)'
136 Added: with: {
137 Added: 7.
138 Added: self dateTimeNow.
139 Added: MFTMuscle trapezius latinName.
140 Added: MFTMuscle brachioRadialis latinName };
141 Added: close
142 Added: ]
143 Added:
63 144 { #category : #'as yet unclassified' }
64 145 MFTSQLite3Database class >> registerNewUser: aMFTUser [
65 146
@@ -74,6 +155,21 @@
74 155 close
75 156 ]
76 157
158 Added: { #category : #'as yet unclassified' }
159 Added: MFTSQLite3Database class >> selectUser: id [
160 Added:
161 Added: | connection user |
162 Added: connection := self defaultConnection.
163 Added: connection open.
164 Added: user := (connection
165 Added: execute: 'SELECT *
166 Added: FROM users
167 Added: WHERE id = ?'
168 Added: value: id) onlyRow.
169 Added: connection close.
170 Added: ^ user
171 Added: ]
172 Added:
77 173 { #category : #setting }
78 174 MFTSQLite3Database class >> storeProspectEmail: anEmail [
79 175
@@ -86,6 +182,20 @@
86 182 ]
87 183
88 184 { #category : #'as yet unclassified' }
185 Added: MFTSQLite3Database >> login: aMFTUser atDateAndTime: aDateAndTime [
186 Added:
187 Added: connection
188 Added: open;
189 Added: execute: 'UPDATE users
190 Added: SET last_login_date_time = ?
191 Added: WHERE username = ?'
192 Added: with: {
193 Added: aDateAndTime.
194 Added: aMFTUser username };
195 Added: close
196 Added: ]
197 Added:
198 Added: { #category : #'as yet unclassified' }
89 199 MFTSQLite3Database >> select: attributes from: aTable [
90 200
91 201 ^ connection execute:
@@ -93,21 +203,41 @@
93 203 ]
94 204
95 205 { #category : #'as yet unclassified' }
206 Added: MFTSQLite3Database >> select: attributes from: aTable where: aCondition [
207 Added:
208 Added: ^ connection execute:
209 Added: 'SELECT ' , attributes , ' FROM ' , aTable asString , 'WHERE '
210 Added: , aCondition
211 Added: ]
212 Added:
213 Added: { #category : #'as yet unclassified' }
96 214 MFTSQLite3Database >> selectAll: aTable [
97 215
98 216 ^ self select: '*' from: aTable
99 217 ]
100 218
101 219 { #category : #'as yet unclassified' }
102 Removed: MFTSQLite3Database >> updateLoginDateAndTime: aMFTUser [
220 Added: MFTSQLite3Database >> selectWeightsWhereUser: aMFTUser [
103 221
104 Removed: connection
105 Removed: open;
106 Removed: execute: 'UPDATE users
107 Removed: SET last_login_date_time = ?
108 Removed: WHERE username = ?'
109 Removed: with: {
110 Removed: DateAndTime now asUTC rounded.
111 Removed: aMFTUser username };
112 Removed: close
222 Added: ^ (self class defaultConnection
223 Added: open;
224 Added: execute: 'SELECT weights.*
225 Added: FROM weights
226 Added: INNER JOIN users ON weights.user_id = users.id
227 Added: WHERE users.username = ?'
228 Added: value: 'foobar123') rows collect: [ :each |
229 Added: MFTWeight newFromDictionary: each asDictionary ]
230 Added: ]
231 Added:
232 Added: { #category : #'as yet unclassified' }
233 Added: MFTSQLite3Database >> selectWorkoutsWhereUser: aMFTUser [
234 Added:
235 Added: ^ (self class defaultConnection
236 Added: open;
237 Added: execute: 'SELECT workouts.*
238 Added: FROM workouts
239 Added: INNER JOIN users ON workouts.user_id = users.id
240 Added: WHERE users.username = ?'
241 Added: value: 'foobar123') rows collect: [ :each |
242 Added: MFTWorkout newFromDictionary: each asDictionary ]
113 243 ]
src/MyFitnessTracker/MFTScreenComponent.class.st
index 08fcbdda..571693a7 100644..100644
@@ -2,8 +2,8 @@
2 2 #name : #MFTScreenComponent,
3 3 #superclass : #SBSComponent,
4 4 #instVars : [
5 Removed: 'header',
6 Removed: 'footer'
5 Added: 'footer',
6 Added: 'pm'
7 7 ],
8 8 #category : #'MyFitnessTracker-Components'
9 9 }
@@ -12,9 +12,9 @@
12 12 MFTScreenComponent >> children [
13 13
14 14 ^ {
15 Removed: header.
16 Removed: self session mainNavigationBar.
17 Removed: self session activePage.
15 Added: pm registerProspectHeader.
16 Added: pm mainNavigationBar.
17 Added: pm activePage.
18 18 footer }
19 19 ]
20 20
@@ -22,16 +22,17 @@
22 22 MFTScreenComponent >> initialize [
23 23
24 24 super initialize.
25 Removed: header := MFTHeaderProspectComponent new.
26 Removed: footer := MFTFooterComponent new
25 Added: pm := self session pageManager.
26 Added: footer := pm defaultFooter
27 27 ]
28 28
29 29 { #category : #rendering }
30 30 MFTScreenComponent >> renderContentOn: html [
31 31
32 Added: self session user isLoggedIn ifFalse: [
33 Added: html render: pm registerProspectHeader ].
32 34 html
33 Removed: render: header;
34 Removed: render: self session mainNavigationBar;
35 Removed: render: self session activePage;
35 Added: render: pm mainNavigationBar;
36 Added: render: pm activePage;
36 37 render: footer
37 38 ]
src/MyFitnessTracker/MFTSession.class.st
index ea65ee9e..1b358065 100644..100644
@@ -4,26 +4,12 @@
4 4 #instVars : [
5 5 'user',
6 6 'db',
7 Removed: 'mainNavigationBar',
8 Removed: 'mainPages',
9 Removed: 'activePage'
7 Added: 'pageManager'
10 8 ],
11 9 #category : #MyFitnessTracker
12 10 }
13 11
14 12 { #category : #accessing }
15 Removed: MFTSession >> activePage [
16 Removed:
17 Removed: ^ activePage
18 Removed: ]
19 Removed:
20 Removed: { #category : #accessing }
21 Removed: MFTSession >> activePage: anObject [
22 Removed:
23 Removed: activePage := anObject
24 Removed: ]
25 Removed:
26 Removed: { #category : #accessing }
27 13 MFTSession >> db [
28 14
29 15 ^ db
@@ -40,38 +26,35 @@
40 26
41 27 super initialize.
42 28 user := MFTUser new.
43 Removed: mainNavigationBar := MFTMainNavigationBarComponent new.
44 Removed: mainPages := OrderedCollection newFromArray: {
45 Removed: MFTPublicLandingPage new.
46 Removed: MFTWorkoutsListPage placeholder.
47 Removed: MFTWeightsListPage new.
48 Removed: MFTLearnPage new.
49 Removed: MFTProfilePage new }.
50 Removed: activePage := mainPages first
29 Added: pageManager := MFTPageManager new.
51 30 ]
52 31
53 Removed: { #category : #accessing }
54 Removed: MFTSession >> mainNavigationBar [
32 Added: { #category : #login }
33 Added: MFTSession >> login: aMFTUser [
55 34
56 Removed: ^ mainNavigationBar
35 Added: | time |
36 Added: time := DateAndTime now asUTC rounded asString.
37 Added: "Reflect the login in database"
38 Added: self db: MFTSQLite3Database newFromDefault.
39 Added: self db connection open.
40 Added: self db login: aMFTUser atDateAndTime: time.
41 Added: "Reflect the login in the session user"
42 Added: self user: aMFTUser.
43 Added: self user lastLoginDateAndTime: time.
44 Added: self user isLoggedIn: true.
45 Added: pageManager loadUserPages
57 46 ]
58 47
59 48 { #category : #accessing }
60 Removed: MFTSession >> mainNavigationBar: anObject [
49 Added: MFTSession >> pageManager [
61 50
62 Removed: mainNavigationBar := anObject
51 Added: ^ pageManager
63 52 ]
64 53
65 54 { #category : #accessing }
66 Removed: MFTSession >> mainPages [
55 Added: MFTSession >> pageManager: aMFTPageManager [
67 56
68 Removed: ^ mainPages
69 Removed: ]
70 Removed:
71 Removed: { #category : #accessing }
72 Removed: MFTSession >> mainPages: anObject [
73 Removed:
74 Removed: mainPages := anObject
57 Added: pageManager := aMFTPageManager
75 58 ]
76 59
77 60 { #category : #'parent/child' }
src/MyFitnessTracker/MFTUser.class.st
index 1315a8a7..53650a16 100644..100644
@@ -36,6 +36,16 @@
36 36 yourself
37 37 ]
38 38
39 Added: { #category : #'instance creation' }
40 Added: MFTUser class >> newFromID: id [
41 Added:
42 Added: | dictionary |
43 Added: dictionary := (MFTSQLite3Database selectUser: id) asDictionary.
44 Added: ^ self
45 Added: newFromDictionary: dictionary;
46 Added: yourself
47 Added: ]
48 Added:
39 49 { #category : #accessing }
40 50 MFTUser >> dateOfBirth [
41 51
src/MyFitnessTracker/MFTUserLandingPage.class.st
index ceb6f194..05ec5a96 100644..100644
@@ -8,18 +8,37 @@
8 8 MFTUserLandingPage >> initialize [
9 9
10 10 super initialize.
11 Removed: title := self session user ifNotNil: [
12 Removed: self session user username capitalized ]
11 Added: title := self session user username
13 12 ]
14 13
15 14 { #category : #rendering }
16 15 MFTUserLandingPage >> renderContentOn: html [
17 16
18 17 html container: [
19 Removed: html displayHeading
20 Removed: level: 1;
21 Removed: with: 'Welcome, ' , self session user username , '!'.
22 Removed: html unorderedList: [
23 Removed: (self session db selectAll: 'prospects') do: [ :prospect |
24 Removed: html listItem: prospect ] ] ]
18 Added: self renderMainCardOn: html.
19 Added: self renderWorkoutsSummaryCardOn: html ]
20 Added: ]
21 Added:
22 Added: { #category : #rendering }
23 Added: MFTUserLandingPage >> renderMainCardOn: html [
24 Added:
25 Added: html card: [
26 Added: html jumbotron
27 Added: class: 'p-3 bg-light';
28 Added: with: [
29 Added: html displayHeading
30 Added: level: 1;
31 Added: with: [
32 Added: html
33 Added: text: 'Welcome, ';
34 Added: code: self session user username;
35 Added: text: '!' ].
36 Added: html big textMuted with:
37 Added: 'Last logged in ' , self session user lastLoginDateAndTime ] ]
38 Added: ]
39 Added:
40 Added: { #category : #rendering }
41 Added: MFTUserLandingPage >> renderWorkoutsSummaryCardOn: html [
42 Added:
43 Added: html card: [ html text: self session user username. ]
25 44 ]
src/MyFitnessTracker/MFTWeight.class.st
index 0ab6c1eb..4ea07c20 100644..100644
@@ -3,7 +3,8 @@
3 3 #superclass : #Object,
4 4 #instVars : [
5 5 'date',
6 Removed: 'value'
6 Added: 'value',
7 Added: 'user'
7 8 ],
8 9 #category : #MyFitnessTracker
9 10 }
@@ -25,6 +26,16 @@
25 26 yourself
26 27 ]
27 28
29 Added: { #category : #'instance creation' }
30 Added: MFTWeight class >> newFromDictionary: aDictionary [
31 Added:
32 Added: ^ self new
33 Added: date: (Date fromString: (aDictionary at: 'date'));
34 Added: user: (MFTUser newFromID: (aDictionary at: 'user_id'));
35 Added: value: (aDictionary at: 'value');
36 Added: yourself
37 Added: ]
38 Added:
28 39 { #category : #accessing }
29 40 MFTWeight class >> value: aValue [
30 41
@@ -50,6 +61,18 @@
50 61
51 62 super initialize.
52 63 date := Date today
64 Added: ]
65 Added:
66 Added: { #category : #accessing }
67 Added: MFTWeight >> user [
68 Added:
69 Added: ^ user
70 Added: ]
71 Added:
72 Added: { #category : #accessing }
73 Added: MFTWeight >> user: aMFTUser [
74 Added:
75 Added: user := aMFTUser
53 76 ]
54 77
55 78 { #category : #evaluating }
src/MyFitnessTracker/MFTWeightDialog.class.st
index 00000000..53b2daf7 000000..100644
@@ -0,0 +1,99 @@
1 Added: Class {
2 Added: #name : #MFTWeightDialog,
3 Added: #superclass : #SBSComponent,
4 Added: #instVars : [
5 Added: 'weight',
6 Added: 'addOrEdit'
7 Added: ],
8 Added: #category : #'MyFitnessTracker-Dialogs'
9 Added: }
10 Added:
11 Added: { #category : #actions }
12 Added: MFTWeightDialog class >> edit: aMFTWeight [
13 Added:
14 Added: ^ self new
15 Added: weight: aMFTWeight;
16 Added: addOrEdit: #edit;
17 Added: yourself
18 Added: ]
19 Added:
20 Added: { #category : #accessing }
21 Added: MFTWeightDialog >> addOrEdit [
22 Added:
23 Added: ^ addOrEdit
24 Added: ]
25 Added:
26 Added: { #category : #accessing }
27 Added: MFTWeightDialog >> addOrEdit: aRole [
28 Added:
29 Added: addOrEdit := aRole
30 Added: ]
31 Added:
32 Added: { #category : #initialization }
33 Added: MFTWeightDialog >> initialize [
34 Added:
35 Added: super initialize.
36 Added: weight := MFTWeight new.
37 Added: addOrEdit := #add
38 Added: ]
39 Added:
40 Added: { #category : #rendering }
41 Added: MFTWeightDialog >> renderContentOn: html [
42 Added:
43 Added: html container: [
44 Added: html heading: self addOrEdit asCapitalizedPhrase , ' weight'.
45 Added: html form with: [
46 Added: self renderDateInputOn: html.
47 Added: self renderWeightInputOn: html.
48 Added: self renderSubmitWeightOn: html ] ]
49 Added: ]
50 Added:
51 Added: { #category : #rendering }
52 Added: MFTWeightDialog >> renderDateInputOn: html [
53 Added:
54 Added: html formGroup: [
55 Added: html label: 'Date'.
56 Added: html dateInput5 formControl
57 Added: value: weight date yyyymmdd;
58 Added: callback: [ :date | weight date: (Date fromString: date) ] ]
59 Added: ]
60 Added:
61 Added: { #category : #rendering }
62 Added: MFTWeightDialog >> renderSubmitWeightOn: html [
63 Added:
64 Added: html formGroup: [
65 Added: html buttonGroup with: [
66 Added: html formButton
67 Added: bePrimary;
68 Added: beSubmit;
69 Added: callback: [ self answer: weight ];
70 Added: with: 'Submit'.
71 Added: html formButton
72 Added: beSecondary;
73 Added: callback: [ self answer: nil ];
74 Added: with: 'Cancel' ] ]
75 Added: ]
76 Added:
77 Added: { #category : #rendering }
78 Added: MFTWeightDialog >> renderWeightInputOn: html [
79 Added:
80 Added: html formGroup: [
81 Added: html label: 'Weight'.
82 Added: html textInput
83 Added: formControl;
84 Added: placeholder: weight value;
85 Added: callback: [ :value | weight value: value ];
86 Added: with: weight value ]
87 Added: ]
88 Added:
89 Added: { #category : #accessing }
90 Added: MFTWeightDialog >> weight [
91 Added:
92 Added: ^ weight
93 Added: ]
94 Added:
95 Added: { #category : #accessing }
96 Added: MFTWeightDialog >> weight: anObject [
97 Added:
98 Added: weight := anObject
99 Added: ]
src/MyFitnessTracker/MFTWeightsListPage.class.st
index dd10e303..5424777d 100644..100644
@@ -11,44 +11,117 @@
11 11 MFTWeightsListPage class >> placeholder [
12 12
13 13 ^ self new
14 Removed: weights: {
15 Removed: (MFTWeight value: 90).
16 Removed: (MFTWeight value: 100).
17 Removed: (MFTWeight value: 102).
18 Removed: (MFTWeight date: (Date fromString: '01/01/2024') value: 105) };
14 Added: weights: (OrderedCollection withAll: {
15 Added: (MFTWeight date: (Date fromString: '01/01/2024') value: 90).
16 Added: (MFTWeight date: (Date fromString: '01/02/2024') value: 91).
17 Added: (MFTWeight date: (Date fromString: '01/03/2024') value: 92).
18 Added: (MFTWeight date: (Date fromString: '01/04/2024') value: 93) });
19 19 yourself
20 20 ]
21 21
22 Added: { #category : #adding }
23 Added: MFTWeightsListPage >> addWeight [
24 Added:
25 Added: | newWeight |
26 Added: newWeight := self call: MFTWeightDialog new.
27 Added: newWeight ifNotNil: [ weights addFirst: newWeight ]
28 Added: ]
29 Added:
30 Added: { #category : #accessing }
31 Added: MFTWeightsListPage >> editWeightAt: i [
32 Added:
33 Added: self call: (MFTWeightDialog edit: (weights at: i))
34 Added: ]
35 Added:
22 36 { #category : #initialization }
23 37 MFTWeightsListPage >> initialize [
24 38
25 39 super initialize.
26 Removed: title := 'weight'.
40 Added: title := 'weights'.
27 41 weights := OrderedCollection new
28 42 ]
29 43
44 Added: { #category : #'as yet unclassified' }
45 Added: MFTWeightsListPage >> loadUserWeights [
46 Added:
47 Added: weights := self session db selectWeightsWhereUser:
48 Added: self session user
49 Added: ]
50 Added:
51 Added: { #category : #'as yet unclassified' }
52 Added: MFTWeightsListPage >> randomWittyCTA [
53 Added:
54 Added: | listOfStrings randomString |
55 Added: listOfStrings := #( 'First time?' 'Get your sweat on!'
56 Added: 'Go go go!' 'Go get ''em, Tiger!' ).
57 Added: randomString := listOfStrings atRandom.
58 Added: ^ randomString
59 Added: ]
60 Added:
61 Added: { #category : #accessing }
62 Added: MFTWeightsListPage >> removeWeightAt: i [
63 Added:
64 Added: weights removeAt: i
65 Added: ]
66 Added:
30 67 { #category : #rendering }
68 Added: MFTWeightsListPage >> renderActionsButtonGroupOn: html [
69 Added:
70 Added: html form: [
71 Added: html buttonGroup: [
72 Added: html formButton
73 Added: bePrimary;
74 Added: callback: [ self addWeight ];
75 Added: with: 'Add weight' ] ]
76 Added: ]
77 Added:
78 Added: { #category : #rendering }
31 79 MFTWeightsListPage >> renderContentOn: html [
32 80
33 Removed: html container with: [
34 Removed: html table
35 Removed: class: 'table table-striped';
81 Added: weights sort: [ :a :b | a date > b date ].
82 Added: html container: [
83 Added: weights ifEmpty: [
84 Added: html jumbotron: [ html heading: self randomWittyCTA ] ].
85 Added: self renderActionsButtonGroupOn: html.
86 Added: self renderWeightsAsTableOn: html ]
87 Added: ]
88 Added:
89 Added: { #category : #rendering }
90 Added: MFTWeightsListPage >> renderWeightActionsButtonGroupOn: html at: i [
91 Added:
92 Added: html form: [
93 Added: html buttonGroup
94 Added: beSmall;
36 95 with: [
37 Removed: weights do: [ :each |
38 Removed: html tableRow: [
39 Removed: html
40 Removed: tableData: each date ddmmyyyy;
41 Removed: tableData: each value ] ] ].
42 Removed: html form: [
43 Removed: html buttonGroup: [
44 96 html formButton
45 Removed: bePrimary;
46 Removed: callback: [ "Edit the weight" ];
97 Added: beSecondary;
98 Added: formControl;
99 Added: callback: [ self editWeightAt: i ];
47 100 with: 'Edit'.
48 101 html formButton
49 102 beDanger;
50 Removed: callback: [ nil ];
51 Removed: with: 'Delete' ] ] ]
103 Added: formControl;
104 Added: callback: [ self removeWeightAt: i ];
105 Added: with: 'Delete' ] ]
106 Added: ]
107 Added:
108 Added: { #category : #rendering }
109 Added: MFTWeightsListPage >> renderWeightsAsTableOn: html [
110 Added:
111 Added: html table
112 Added: class: 'table table-striped';
113 Added: with: [
114 Added: html tableHead: [
115 Added: html
116 Added: tableHeading;
117 Added: tableHeading: 'Date';
118 Added: tableHeading: 'Value' ].
119 Added: weights doWithIndex: [ :weight :i |
120 Added: html tableRow: [
121 Added: html tableData: [
122 Added: self renderWeightActionsButtonGroupOn: html at: i ].
123 Added: html tableData: weight date.
124 Added: html tableData: weight value ] ] ]
52 125 ]
53 126
54 127 { #category : #accessing }
src/MyFitnessTracker/MFTWorkout.class.st
index 44457cf9..380132af 100644..100644
@@ -10,6 +10,18 @@
10 10 #category : #'MyFitnessTracker-Model'
11 11 }
12 12
13 Added: { #category : #'instance creation' }
14 Added: MFTWorkout class >> newFromDictionary: aDictionary [
15 Added:
16 Added: ^ self new
17 Added: date: (Date fromString: (aDictionary at: 'date'));
18 Added: primaryMuscle:
19 Added: (MFTMuscle new latinName: (aDictionary at: 'primary_muscle'));
20 Added: secondaryMuscles:
21 Added: (MFTMuscle new latinName: (aDictionary at: 'secondary_muscles'));
22 Added: yourself
23 Added: ]
24 Added:
13 25 { #category : #accessing }
14 26 MFTWorkout class >> primaryMuscle: aMFTMuscle [
15 27
src/MyFitnessTracker/MFTWorkoutDialog.class.st
index fef996c0..c0eab81c 100644..100644
@@ -91,7 +91,8 @@
91 91 MFTWorkoutDialog >> renderContentOn: html [
92 92
93 93 html container: [
94 Removed: html heading: 'Add workout'.
94 Added: html heading: self addOrEdit asCapitalizedPhrase , ' workout for '
95 Added: , workout date asString.
95 96 html form: [
96 97 self renderDateInputOn: html.
97 98 self renderExercisesTableOn: html.
src/MyFitnessTracker/MFTWorkoutsListPage.class.st
index 965ac030..d315fb21 100644..100644
@@ -118,6 +118,13 @@
118 118 ]
119 119
120 120 { #category : #'as yet unclassified' }
121 Added: MFTWorkoutsListPage >> loadUserWorkouts [
122 Added:
123 Added: workouts := self session db selectWorkoutsWhereUser:
124 Added: self session user
125 Added: ]
126 Added:
127 Added: { #category : #'as yet unclassified' }
121 128 MFTWorkoutsListPage >> randomWittyCTA [
122 129
123 130 | listOfStrings randomString |
@@ -190,6 +197,12 @@
190 197 html accordionBody with: [
191 198 html render: workout.
192 199 self renderWorkoutActionsButtonGroup: html at: i ] ] ] ] ]
200 Added: ]
201 Added:
202 Added: { #category : #'as yet unclassified' }
203 Added: MFTWorkoutsListPage >> userWorkouts [
204 Added:
205 Added: self session db selectWorkoutsWhereUser: self session user
193 206 ]
194 207
195 208 { #category : #accessing }