Revision 123fd2f5

b/invitations/invitations.py
319 319
        log.exception(e)
320 320
        return HttpResponseServerError("Error sending invitation email")
321 321

  
322
    return HttpResponse("Invitation has been resent")
322
    return HttpResponse('{"resent":true}')
323 323

  
324 324

  
325 325
def get_invitee_level(source):
b/invitations/templates/invitations.html
14 14
                "updated": "{{ inv.updated|date }}",
15 15
                "accepted": {% if inv.accepted %}true{% else %}false{% endif %},
16 16
                "targetname": "{{ inv.targetname|escapejs }}",
17
                "target": "{{ inv.target|escapejs }}"
17
                "target": "{{ inv.target|escapejs }}",
18
                "id": "{{ inv.id }}"
18 19
            }{% if not forloop.last %},{% endif %}
19 20
        {% endfor %}
20 21
    ],
b/ui/static/snf/css/main.css
5707 5707
.invitations-form .send-error {
5708 5708
    font-size: 0.8em;
5709 5709
    margin-bottom: 5px;
5710
    margin-top: 5px;
5710 5711
    color: #800;
5711 5712
    padding: 3px;
5713
    padding-left: 0;
5712 5714
    display: none;
5713 5715
}
5714 5716

  
......
5729 5731
    position: relative;
5730 5732
}
5731 5733

  
5734
.invitations-form h3 {
5735
    float: left;
5736
}
5737

  
5732 5738
.invitations-form .add-new-invitation {
5733
    position: absolute;
5739
    float:left;
5734 5740
    top:2px;
5735 5741
    right: 20px;
5736 5742
    width: 16px;
......
5738 5744
    background-image: url("../images/option-action-add.png");
5739 5745
    background-repeat: no-repeat;
5740 5746
    background-position: center;
5741
    text-indent: 2000em;
5747
    text-indent: -50000em;
5742 5748
    background-color: #4085A5;
5749
    margin-left: 15px;
5750
    cursor: pointer;
5743 5751
}
5744 5752

  
5745 5753
.invitations-view .left {
......
5761 5769
}
5762 5770

  
5763 5771
.invitations-wrapper .invitations-list {
5764
    width: 33%;
5772
    width: 40%;
5765 5773
    float: left;
5766 5774
    padding-left: 10px;
5767 5775
    border-left: 1px solid #4085A5;
......
5769 5777
}
5770 5778

  
5771 5779
.invitations-wrapper .invitations-form {
5772
    width: 65%;
5780
    width: 57%;
5773 5781
    float: left;
5774 5782
    border-right: 1px solid #4085A5;
5775 5783
}
......
5791 5799
}
5792 5800

  
5793 5801
.invitations-wrapper input.name {
5794
    width: 180px;
5802
    width: 150px;
5795 5803
}
5796 5804

  
5797 5805
.invitations-wrapper input {
5798 5806
    font-size: 0.9em;
5799
    width: 160px;
5807
    width: 155px;
5800 5808
    padding:4px;
5801 5809
}
5802 5810

  
......
5804 5812
    border-bottom: 1px solid #A1C8DB;
5805 5813
    padding-bottom: 5px;
5806 5814
    margin-bottom: 5px;
5815
    position: relative;
5807 5816
}
5808 5817

  
5809 5818
.invitations-view h3 {
......
5817 5826
}
5818 5827

  
5819 5828

  
5820
.invitations-list .email {
5829
.invitations-list .invitation-sent .email {
5821 5830
    color: #D98147;
5822 5831
    font-size: 0.8em;
5823 5832
    font-weight: bold;
5824 5833
    margin-top: 2px;
5825 5834
}
5826 5835

  
5836
.invitations-list .pagination a,
5837
.invitations-list .pagination span {
5838
    padding: 0.2em 0.3em;
5839
}
5840

  
5841
.invitations-list .pagination {
5842
    font-size: 0.75em;
5843
}
5844

  
5827 5845
.invitations-list .name {
5828 5846
    color: #444;
5829 5847
    font-size: 0.9em;
5830 5848
}
5849

  
5850
.invitations-list .status.sending {
5851
    background-image: url("../images/icons/indicators/small/progress.gif");
5852
}
5853

  
5854
.invitations-list .status.sent {
5855
    background-image: url("../images/invitation_accepted.png");
5856
}
5857

  
5858
.invitations-list .status.resend {
5859
    background-image: url("../images/resend.png");
5860
}
5861

  
5862
.invitations-list .status.resend {
5863
    cursor: pointer;
5864
}
5865

  
5866
.invitations-list .status {
5867
    width: 20px;
5868
    height: 15px;
5869
    position: absolute;
5870
    top:5px;
5871
    right: 2px;
5872
    background-repeat: no-repeat;
5873
    background-position: center;
5874
}
5875

  
5876
.invitations-form .form-entry:last-child {
5877
    border-bottom: none;
5878
}
5879

  
5880
.invitations-form .form-entry {
5881
    position: relative;
5882
    margin-bottom: 10px;
5883
    padding-bottom: 10px;
5884
    border-bottom: 1px solid #ddd;
5885
    margin-right: 10px;
5886
}
5887

  
5888
.invitations-form .send-invitations {
5889
    float: left;
5890
    padding: 5px;
5891
    color: #fff;
5892
    margin-right: 10px;
5893
    margin-top: 10px;
5894
}
5895

  
5896
.invitations-form .form-entry.error {
5897
    padding-bottom: 0px;
5898
}
5899

  
5900
.invitations-form .form-entry.error .remove-invitation {
5901
    bottom: 32px;
5902
}
5903

  
5904
.invitations-form .remove-invitation {
5905
    padding: 5px;
5906
    background-image: url("../images/option-action-remove.png");
5907
    background-position: center;
5908
    background-repeat: no-repeat;
5909
    color: #fff;
5910
    width: 15px;
5911
    height: 15px;
5912
    cursor: pointer;
5913
    text-indent: -50000px;
5914
    position: absolute;
5915
    right: -10px;
5916
    bottom: 14px;
5917
}
5918

  
5919
.invitations-list .resent-info,
5920
.invitations-form .top-info {
5921
    font-size: 0.8em;
5922
    margin-bottom: 10px;
5923
}
5924

  
5925
.invitations-list .msg .email,
5926
.invitations-form .success .msg .email {
5927
    font-weight: bold;
5928
}
5929

  
5930
.invitations-list .msg.err-msg,
5931
.invitations-list .success.msg,
5932
.invitations-form .success .msg {
5933
    background-color: #080;
5934
    color: white;
5935
    padding: 5px;
5936
    margin-right: 10px;
5937
    margin-bottom: 5px;
5938
}
5939
.invitations-list .msg.err-msg,
5940
.invitations-list .success.msg {
5941
    margin-right: 0px;
5942
}
5943
.invitations-list .msg.err-msg {
5944
    background-color: #800;
5945
}
b/ui/static/snf/js/ui/web/ui_invitations_view.js
24 24
        overlay_id: "invitations-overlay",
25 25

  
26 26
        subtitle: "",
27
        title: "Invitations",
27
        title: "Invite friends",
28 28

  
29 29
        initialize: function(options) {
30 30
            views.InvitationsView.__super__.initialize.apply(this, arguments);
......
45 45
            this.inv_sent_per_page = 9;
46 46

  
47 47
            this.init_handlers();
48

  
49
            this.invitations_retrieved = false;
50
            this.loading_invitations = this.$(".loading-invitations");
48 51
        },
49 52

  
50 53
        init_handlers: function() {
51 54
            var self = this;
52
            this.add.click(this.add_new_entry);
55
            this.add.click(function(){
56
                self.add_new_entry().find("input.name").focus();
57
            });
53 58
            this.send.click(this.send_entries);
54 59
            this.remove.live('click', function() {
55 60
                return self.remove_entry($(this).parent().parent());
56 61
            });
62

  
57 63
        },
58 64
        
59 65
        remove_entry: function(entry) {
......
64 70

  
65 71
        add_new_entry: function() {
66 72
            var new_entry = this.create_form_entry().show()
73

  
74
            var name = "inv-entry-" + this.get_entries().length;
75
            new_entry.find("input.name").attr("name", "name-" + name);
76
            new_entry.find("input.email").attr("name", "email-" + name);
77
            
78
            var self = this;
79
            new_entry.find("input").bind("keydown", function(e){
80
                e.keyCode = e.keyCode || e.which;
81
                if (e.keyCode == 13) { self.send_entries() };
82
            })
83

  
67 84
            this.form_entries.append(new_entry).show();
68
            $(new_entry.find("input").get(0)).focus();
69 85
            this.fix_entries();
86
            return new_entry;
70 87
        },
71 88
        
72 89
        show_entry_error: function(entry, error) {
......
84 101
        entry_is_valid: function(entry) {
85 102
            var data = this.get_entry_data(entry);
86 103

  
104
            if (data.name == "" && data.email == "") {
105
                return false;
106
            }
107

  
87 108
            entry.find(".send-error").hide();
88 109
            entry.removeClass("error");
89 110
            entry.find("input").removeClass("has-errors");
......
92 113
            if (!data.name || data.name.split(" ").length == 1) {
93 114
                error = "Invalid name";
94 115
                entry.find("input.name").addClass("has-errors");
116
                entry.find("input.name").focus();
95 117
            }
96 118

  
97 119
            var reg = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
98 120
            if (!data.email || reg.test(data.email) == false) {
99 121
                error = "Invalid email";
100 122
                entry.find("input.email").addClass("has-errors");
123
                entry.find("input.email").focus();
101 124
            }
102 125
            
103 126
            if (error) { this.show_entry_error(entry, error) };
......
108 131
            var self = this;
109 132
            this.form_entries.find(".form-entry").each(function(index, el) { self.entry_is_valid($(el)) });
110 133
            var entries_to_send = this.form_entries.find(".form-entry:not(.error):not(.sending)");
111
            this._send_entries(entries_to_send);
134

  
135
            entries = _.filter(entries_to_send, function(e){
136
                var data = self.get_entry_data($(e));
137
                if (data.name == "" && data.email == "") {
138
                    return false;
139
                }
140
                return true;
141
            })
142
            this._send_entries(entries);
112 143
        },
113 144

  
114 145
        _send_entries: function(entries) {
......
126 157

  
127 158
        invitation_send: function(entry, data) {
128 159
            entry.removeClass("sending");
129
            if (data.errors && data.errors.length) {
130
                this.show_entry_error($(entry), data.errors[0]);
160
            if (data.errors && data.errors.length > 0) {
161
                this.show_entry_error($(entry), data.errors[0].msg);
131 162
                return;
132 163
            } else {
133 164
                entry.remove();
......
136 167
        },
137 168

  
138 169
        show_send_success: function(to, data) {
139
            var msg = "Invitation to " + to + " was sent.";
170
            var msg = 'Invitation to <span class="email">' + to + '</span> was sent.';
140 171
            var msg_el = $('<div class="msg">{0}</div>'.format(msg));
141 172

  
142 173
            this.top_info.append(msg_el);
143 174

  
144 175
            window.setTimeout(function(){
145 176
                msg_el.fadeOut(600, function(){$(this).remove()});
146
            }, 2000);
177
            }, 5000);
147 178

  
148 179
            this.fix_entries();
149 180
            this.reset_invitations_sent();
......
175 206
            this.$(".remove-invitation").hide();
176 207
            if (this.get_entries().length == 0) {
177 208
                this.add_new_entry();
209
                this.add_new_entry();
178 210
            }
179 211

  
180 212
            if (this.get_entries().length > 1) {
......
183 215
            this.$(".form-entry:first-child label").show();
184 216
            this.$(".form-entry:not(:first-child) label").hide();
185 217
        },
186

  
218
        
187 219
        show: function() {
188 220
            views.InvitationsView.__super__.show.apply(this, arguments);
189 221
            this.current_page = 0;
190
            this.reset_invitations_sent();
222
            this.reset_sent();
191 223
            this.reset();
192 224

  
193 225
            this.add_new_entry();
194 226
            this.add_new_entry();
195
            this.add_new_entry();
227

  
228
            if (this.invitations_retrieved) {
229
                this.loading_invitations.hide();
230
            }
231
        },
232

  
233
        reset_sent: function() {
234
            this.reset_invitations_sent();
235
            this.add_invitations_sent([]);
196 236
        },
197 237

  
198 238
        create_form_entry: function() {
......
218 258
            var url = snf.config.invitations_url;
219 259
            params = {
220 260
                success: function(data) {
261
                    self.invitations_retrieved = true;
262
                    self.loading_invitations.hide();
263

  
221 264
                    if (!data || !data.invitations) {
222 265
                        self.show_invitations_sent_error();
223 266
                    } else {
......
244 287

  
245 288
            snf.api.sync("read", undefined, params);
246 289
        },
290
        
291
        resend_succeed: function(inv, el) {
292
            el.find(".status.sent").removeClass("hidden").hide();
293
            el.find(".status.resend").removeClass("hidden").show();
294
            el.find(".status.sending").removeClass("hidden").hide();
295

  
296
            var msg = $('<div class="msg success">Invitation has been resent to <span class="email">{0}</span>.</div>'.format(inv.target));
297
            this.$(".resent-info").append(msg);
298
            setTimeout(function(){ $(msg).fadeOut(600)}, 5000);
299
        },
300

  
301
        resend_failed: function(inv, el) {
302
            el.find(".status.sent").removeClass("hidden").hide();
303
            el.find(".status.resend").removeClass("hidden").show();
304
            el.find(".status.sending").removeClass("hidden").hide();
305
            
306
            var msg = $('<div class="msg err-msg">Resend to <span class="email">{0}</span> failed.</div>'.format(inv.target));
307
            this.$(".resent-info").append(msg);
308
            setTimeout(function(){ $(msg).fadeOut(600)}, 5000);
309
        },
310

  
311
        resend_invitation: function(id, el, inv) {
312
            var self = this;
313
            var inv = inv;
314
            var id = id;
315
            var el = el;
316

  
317
            el.find(".status.sent").removeClass("hidden").hide();
318
            el.find(".status.resend").removeClass("hidden").hide();
319
            el.find(".status.sending").removeClass("hidden").show();
320

  
321
            var url = snf.config.invitations_url + "/resend/";
322
            var payload = "invid=" + id;
323
            params = {
324
                success: function(data) {
325
                    self.resend_succeed(inv, el);
326
                },
327
                error: function() {
328
                    self.resend_failed(inv, el);
329
                },
330
                data: payload,
331
                url: url,
332
                skip_api_error: true
333
            }
334

  
335
            snf.api.sync("create", undefined, params);
336
        },
247 337

  
248 338
        add_invitations_sent: function(invs) {
339
            var self = this;
249 340
            _.each(invs, _.bind(function(inv) {
250 341
                var el = this.new_invitation_sent_el();
342
                var invitation = inv;
343

  
251 344
                el.find(".name").text(inv.targetname);
252 345
                el.find(".email").text(inv.target);
253
                el.find(".action").addClass("sent");
346
                
347
                el.find(".status.sent").removeClass("hidden").show();
348
                el.find(".status.resend").removeClass("hidden").hide();
349
                el.find(".status.sending").removeClass("hidden").hide();
350

  
254 351
                if (!inv.accepted) {
255
                    el.find(".action").removeClass("resend").addClass("resend")
352
                    el.find(".status.resend").show();
353
                    el.find(".status.sent").hide();
354
                    el.find(".status.sending").hide();
256 355
                }
356

  
357
                el.find(".status.resend").click(function(){
358
                    self.resend_invitation(invitation.id, el, invitation);
359
                })
360

  
257 361
                el.removeClass("hidden");
258 362
                this.sent.append(el);
259 363
            }, this));
260 364
            this.update_pagination();
261 365
            this.sent_pages.trigger("setPage", this.current_page || 0);
262 366
        },
367

  
368
        onOpen: function() {
369
            views.InvitationsView.__super__.onOpen.apply(this, arguments);
370
            setTimeout(function(){$(this.$("input.name:visible").get(0)).focus()}, 100);
371
        },
263 372
        
264 373
        inv_sent_per_page: 5,
265 374
        update_pagination: function() {
266 375
            this.sent.css({minHeight:this.inv_sent_per_page * 35 + "px"})
267
            this.sent_pages.pagination(this.sent.children().length, {items_per_page:this.inv_sent_per_page, callback: this.page_cb});
376
            this.sent_pages.pagination(this.sent.children().length, 
377
                                       {items_per_page:this.inv_sent_per_page, callback: this.page_cb});
268 378
        },
269 379

  
270 380
        page_cb: function(index, pager) {
b/ui/templates/home.html
544 544
            synnefo.ui.init();
545 545

  
546 546
            synnefo.ui.main.bind("initial", function(){
547
                $(".usermenu .invitations").click();
548 547
            })
549 548

  
550 549
        })
b/ui/templates/partials/invitations.html
1 1
{% load i18n %}
2 2
<div id="invitations-overlay-content" class="hidden overlay-content invitations-view">
3 3
    <div class="description">
4
        <p>You have <span class="left"></span> invitations left</p>
4
        <p>You have <span class="left">...</span> invitations left</p>
5 5
    </div>
6 6
    <div class="invitations-wrapper clearfix">
7 7
        <div class="invitations-form clearfix">
8
            <div class="invitations-send-header clearfix">
8 9
            <h3>{% trans "Send new intivations" %}</h3>
9
            <div class="add-new-invitation">add</div>
10
            <div class="add-new-invitation" title="{% trans "Add new invitation" %}">add</div>
11
            </div>
10 12
            <div class="top-info success"></div>
11 13
            <div class="form-entry-tpl clearfix hidden">
12 14
                <div class="fields clearfix">
......
18 20
                        <label>Email</label>
19 21
                        <input type="text" class="email"/>
20 22
                    </div>
21
                    <div class="remove-invitation">remove</div>
23
                    <div class="remove-invitation" title="{% trans "Remove invitation" %}">remove</div>
22 24
                </div>
23 25
                <div class="send-error"></div>
24 26
                <div class="sending-msg hidden">{% trans "Sending..." %}</div>
25 27
            </div>
26 28
            <div class="form-entries">
27 29
            </div>
28
            <div class="send-invitations">send invitations</div>
30
            <div vlcass="form-actions">
31
                <span class="form-action create send-invitations">send invitations</span>
32
            </div>
29 33
        </div>
30 34
        <div class="invitations-list clearfix">
31 35
            <h3>{% trans "Intivations sent" %}</h3>
32
            <div class="invitation-sent-tpl hidden">
33
                <div class="action resend"></div>
36
            <div class="resent-info"></div>
37
            <div class="loading-invitations">Loading...</div>
38
            <div class="invitation-sent-tpl hidden clearfix">
34 39
                <div class="name"></div>
35 40
                <div class="email"></div>
41
                <div class="status resend hidden" title="{% trans "Resend invitation" %}"></div>
42
                <div class="status sent hidden" title="{% trans "Invitation accepted" %}"></div>
43
                <div class="status sending hidden"></div>
36 44
            </div>
37
            <div class="invitations-sent-cont">
38
            </div>
45
            <form>
46
                <div class="invitations-sent-cont">
47
                </div>
48
            </form>
39 49
            <div class="invitations-sent-pages"></div>
40 50
            <div class="hidden invitations-sent-error">{% trans "Cannot display invitations history" %}</div>
41 51
        </div>

Also available in: Unified diff