Browse Source

feat(newsletter): add newsletter service, endpoints, and UI components

Add Newsletter service to support functionality for unfollowing newsletters
Add Newsletter REST controller and routing
Implement newsletter-related endpoints and methods in the User Service
Create UnfollowRequest for the INewsletterService interface
Add MyListNewsletterResponse to user's data fields
Add newsletter validation
Refactor JS components to support newsletter type and simplify recipient forms by moving logic to FormRecipient component
Refactor window global constants to support newsletters
Modify server to initialize and use the newsletter services
Add UI component for listing newsletters
Refactor existing components to use FormRecipient for recipient data input
pull/204/head
Aldino Kemal 1 year ago
parent
commit
dd4ab8f010
  1. 2
      src/cmd/root.go
  2. 11
      src/domains/newletter/newsletter.go
  3. 4
      src/domains/user/account.go
  4. 1
      src/domains/user/user.go
  5. 32
      src/internal/rest/newsletter.go
  6. 13
      src/internal/rest/user.go
  7. 22
      src/pkg/whatsapp/whatsapp.go
  8. 32
      src/services/newsletter.go
  9. 14
      src/services/user.go
  10. 20
      src/validations/newsletter_validation.go
  11. 25
      src/views/components/AccountAvatar.js
  12. 24
      src/views/components/AccountUserInfo.js
  13. 26
      src/views/components/MessageDelete.js
  14. 26
      src/views/components/MessageReact.js
  15. 26
      src/views/components/MessageRevoke.js
  16. 26
      src/views/components/MessageUpdate.js
  17. 115
      src/views/components/NewsletterList.js
  18. 25
      src/views/components/SendAudio.js
  19. 26
      src/views/components/SendContact.js
  20. 26
      src/views/components/SendFile.js
  21. 26
      src/views/components/SendImage.js
  22. 26
      src/views/components/SendLocation.js
  23. 25
      src/views/components/SendMessage.js
  24. 26
      src/views/components/SendPoll.js
  25. 26
      src/views/components/SendVideo.js
  26. 52
      src/views/components/generic/FormRecipient.js
  27. 17
      src/views/index.html

2
src/cmd/root.go

@ -113,6 +113,7 @@ func runRest(_ *cobra.Command, _ []string) {
userService := services.NewUserService(cli) userService := services.NewUserService(cli)
messageService := services.NewMessageService(cli) messageService := services.NewMessageService(cli)
groupService := services.NewGroupService(cli) groupService := services.NewGroupService(cli)
newsletterService := services.NewNewsletterService(cli)
// Rest // Rest
rest.InitRestApp(app, appService) rest.InitRestApp(app, appService)
@ -120,6 +121,7 @@ func runRest(_ *cobra.Command, _ []string) {
rest.InitRestUser(app, userService) rest.InitRestUser(app, userService)
rest.InitRestMessage(app, messageService) rest.InitRestMessage(app, messageService)
rest.InitRestGroup(app, groupService) rest.InitRestGroup(app, groupService)
rest.InitRestNewsletter(app, newsletterService)
app.Get("/", func(c *fiber.Ctx) error { app.Get("/", func(c *fiber.Ctx) error {
return c.Render("views/index", fiber.Map{ return c.Render("views/index", fiber.Map{

11
src/domains/newletter/newsletter.go

@ -0,0 +1,11 @@
package newletter
import "context"
type INewsletterService interface {
Unfollow(ctx context.Context, request UnfollowRequest) (err error)
}
type UnfollowRequest struct {
NewsletterID string `json:"newsletter_id" form:"newsletter_id"`
}

4
src/domains/user/account.go

@ -48,3 +48,7 @@ type MyPrivacySettingResponse struct {
type MyListGroupsResponse struct { type MyListGroupsResponse struct {
Data []types.GroupInfo `json:"data"` Data []types.GroupInfo `json:"data"`
} }
type MyListNewsletterResponse struct {
Data []types.NewsletterMetadata `json:"data"`
}

1
src/domains/user/user.go

@ -8,5 +8,6 @@ type IUserService interface {
Info(ctx context.Context, request InfoRequest) (response InfoResponse, err error) Info(ctx context.Context, request InfoRequest) (response InfoResponse, err error)
Avatar(ctx context.Context, request AvatarRequest) (response AvatarResponse, err error) Avatar(ctx context.Context, request AvatarRequest) (response AvatarResponse, err error)
MyListGroups(ctx context.Context) (response MyListGroupsResponse, err error) MyListGroups(ctx context.Context) (response MyListGroupsResponse, err error)
MyListNewsletter(ctx context.Context) (response MyListNewsletterResponse, err error)
MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error) MyPrivacySetting(ctx context.Context) (response MyPrivacySettingResponse, err error)
} }

32
src/internal/rest/newsletter.go

@ -0,0 +1,32 @@
package rest
import (
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils"
"github.com/gofiber/fiber/v2"
)
type Newsletter struct {
Service domainNewsletter.INewsletterService
}
func InitRestNewsletter(app *fiber.App, service domainNewsletter.INewsletterService) Newsletter {
rest := Newsletter{Service: service}
app.Post("/newsletter/unfollow", rest.Unfollow)
return rest
}
func (controller *Newsletter) Unfollow(c *fiber.Ctx) error {
var request domainNewsletter.UnfollowRequest
err := c.BodyParser(&request)
utils.PanicIfNeeded(err)
err = controller.Service.Unfollow(c.UserContext(), request)
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success unfollow newsletter",
})
}

13
src/internal/rest/user.go

@ -17,6 +17,7 @@ func InitRestUser(app *fiber.App, service domainUser.IUserService) User {
app.Get("/user/avatar", rest.UserAvatar) app.Get("/user/avatar", rest.UserAvatar)
app.Get("/user/my/privacy", rest.UserMyPrivacySetting) app.Get("/user/my/privacy", rest.UserMyPrivacySetting)
app.Get("/user/my/groups", rest.UserMyListGroups) app.Get("/user/my/groups", rest.UserMyListGroups)
app.Get("/user/my/newsletters", rest.UserMyListNewsletter)
return rest return rest
} }
@ -80,3 +81,15 @@ func (controller *User) UserMyListGroups(c *fiber.Ctx) error {
Results: response, Results: response,
}) })
} }
func (controller *User) UserMyListNewsletter(c *fiber.Ctx) error {
response, err := controller.Service.MyListNewsletter(c.UserContext())
utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{
Status: 200,
Code: "SUCCESS",
Message: "Success get list newsletter",
Results: response,
})
}

22
src/pkg/whatsapp/whatsapp.go

@ -107,17 +107,19 @@ func ParseJID(arg string) (types.JID, error) {
} }
if !strings.ContainsRune(arg, '@') { if !strings.ContainsRune(arg, '@') {
return types.NewJID(arg, types.DefaultUserServer), nil return types.NewJID(arg, types.DefaultUserServer), nil
} else {
recipient, err := types.ParseJID(arg)
if err != nil {
fmt.Printf("invalid JID %s: %v", arg, err)
return recipient, pkgError.ErrInvalidJID
} else if recipient.User == "" {
fmt.Printf("invalid JID %v: no server specified", arg)
return recipient, pkgError.ErrInvalidJID
}
return recipient, nil
} }
recipient, err := types.ParseJID(arg)
if err != nil {
fmt.Printf("invalid JID %s: %v", arg, err)
return recipient, pkgError.ErrInvalidJID
}
if recipient.User == "" {
fmt.Printf("invalid JID %v: no server specified", arg)
return recipient, pkgError.ErrInvalidJID
}
return recipient, nil
} }
func IsOnWhatsapp(waCli *whatsmeow.Client, jid string) bool { func IsOnWhatsapp(waCli *whatsmeow.Client, jid string) bool {

32
src/services/newsletter.go

@ -0,0 +1,32 @@
package services
import (
"context"
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter"
"github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp"
"github.com/aldinokemal/go-whatsapp-web-multidevice/validations"
"go.mau.fi/whatsmeow"
)
type newsletterService struct {
WaCli *whatsmeow.Client
}
func NewNewsletterService(waCli *whatsmeow.Client) domainNewsletter.INewsletterService {
return &newsletterService{
WaCli: waCli,
}
}
func (service newsletterService) Unfollow(ctx context.Context, request domainNewsletter.UnfollowRequest) (err error) {
if err = validations.ValidateUnfollowNewsletter(ctx, request); err != nil {
return err
}
JID, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.NewsletterID)
if err != nil {
return err
}
return service.WaCli.UnfollowNewsletter(JID)
}

14
src/services/user.go

@ -127,6 +127,20 @@ func (service userService) MyListGroups(_ context.Context) (response domainUser.
return response, nil return response, nil
} }
func (service userService) MyListNewsletter(_ context.Context) (response domainUser.MyListNewsletterResponse, err error) {
whatsapp.MustLogin(service.WaCli)
datas, err := service.WaCli.GetSubscribedNewsletters()
if err != nil {
return
}
fmt.Printf("%+v\n", datas)
for _, data := range datas {
response.Data = append(response.Data, *data)
}
return response, nil
}
func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) { func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) {
whatsapp.MustLogin(service.WaCli) whatsapp.MustLogin(service.WaCli)

20
src/validations/newsletter_validation.go

@ -0,0 +1,20 @@
package validations
import (
"context"
domainNewsletter "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/newletter"
pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error"
validation "github.com/go-ozzo/ozzo-validation/v4"
)
func ValidateUnfollowNewsletter(ctx context.Context, request domainNewsletter.UnfollowRequest) error {
err := validation.ValidateStructWithContext(ctx, &request,
validation.Field(&request.NewsletterID, validation.Required),
)
if err != nil {
return pkgError.ValidationError(err.Error())
}
return nil
}

25
src/views/components/AccountAvatar.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'AccountAvatar', name: 'AccountAvatar',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
image: null, image: null,
loading: false, loading: false,
@ -12,7 +17,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -45,7 +50,7 @@ export default {
handleReset() { handleReset() {
this.phone = ''; this.phone = '';
this.image = null; this.image = null;
this.type = 'user';
this.type = window.TYPEUSER;
} }
}, },
template: ` template: `
@ -66,19 +71,7 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Preview</label> <label>Preview</label>

24
src/views/components/AccountUserInfo.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'AccountUserInfo', name: 'AccountUserInfo',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
// //
name: null, name: null,
@ -15,7 +20,7 @@ export default {
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -52,7 +57,7 @@ export default {
this.name = null; this.name = null;
this.status = null; this.status = null;
this.devices = []; this.devices = [];
this.type = 'user';
this.type = window.TYPEUSER;
} }
}, },
template: ` template: `
@ -74,18 +79,7 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<button type="button" class="ui primary button" :class="{'loading': loading}" <button type="button" class="ui primary button" :class="{'loading': loading}"
@click="handleSubmit"> @click="handleSubmit">

26
src/views/components/MessageDelete.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'DeleteMessage', name: 'DeleteMessage',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
message_id: '', message_id: '',
loading: false, loading: false,
@ -10,7 +15,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -48,7 +53,7 @@ export default {
} }
}, },
handleReset() { handleReset() {
this.type = 'user';
this.type = window.TYPEUSER;
this.phone = ''; this.phone = '';
this.message_id = ''; this.message_id = '';
this.new_message = ''; this.new_message = '';
@ -74,19 +79,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Message ID</label> <label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id" <input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageReact.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'ReactMessage', name: 'ReactMessage',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
message_id: '', message_id: '',
emoji: '', emoji: '',
@ -11,7 +16,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -51,7 +56,7 @@ export default {
this.phone = ''; this.phone = '';
this.message_id = ''; this.message_id = '';
this.emoji = ''; this.emoji = '';
this.type = 'user';
this.type = window.TYPEUSER;
}, },
}, },
template: ` template: `
@ -74,19 +79,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Message ID</label> <label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id" <input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageRevoke.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'Message', name: 'Message',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
message_id: '', message_id: '',
loading: false, loading: false,
@ -10,7 +15,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -49,7 +54,7 @@ export default {
handleReset() { handleReset() {
this.phone = ''; this.phone = '';
this.message_id = ''; this.message_id = '';
this.type = 'user';
this.type = window.TYPEUSER;
}, },
}, },
template: ` template: `
@ -71,19 +76,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label> Message ID</label> <label> Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id" <input v-model="message_id" type="text" placeholder="Please enter your message id"

26
src/views/components/MessageUpdate.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'UpdateMessage', name: 'UpdateMessage',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
message_id: '', message_id: '',
new_message: '', new_message: '',
@ -11,7 +16,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -49,7 +54,7 @@ export default {
} }
}, },
handleReset() { handleReset() {
this.type = 'user';
this.type = window.TYPEUSER;
this.phone = ''; this.phone = '';
this.message_id = ''; this.message_id = '';
this.new_message = ''; this.new_message = '';
@ -75,19 +80,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Message ID</label> <label>Message ID</label>
<input v-model="message_id" type="text" placeholder="Please enter your message id" <input v-model="message_id" type="text" placeholder="Please enter your message id"

115
src/views/components/NewsletterList.js

@ -0,0 +1,115 @@
export default {
name: 'ListNewsletter',
data() {
return {
newsletters: []
}
},
methods: {
async openModal() {
try {
this.dtClear()
await this.submitApi();
$('#modalNewsletterList').modal('show');
this.dtRebuild()
showSuccessInfo("Newsletters fetched")
} catch (err) {
showErrorInfo(err)
}
},
dtClear() {
$('#account_newsletters_table').DataTable().destroy();
},
dtRebuild() {
$('#account_newsletters_table').DataTable({
"pageLength": 100,
"reloadData": true,
}).draw();
},
async handleUnfollowNewsletter(newsletter_id) {
try {
const ok = confirm("Are you sure to leave this newsletter?");
if (!ok) return;
await this.unfollowNewsletterApi(newsletter_id);
this.dtClear()
await this.submitApi();
this.dtRebuild()
showSuccessInfo("Success unfollow newsletter")
} catch (err) {
showErrorInfo(err)
}
},
async unfollowNewsletterApi(newsletter_id) {
try {
let payload = new FormData();
payload.append("newsletter_id", newsletter_id)
await window.http.post(`/newsletter/unfollow`, payload)
} catch (error) {
if (error.response) {
throw new Error(error.response.data.message);
}
throw new Error(error.message);
}
},
async submitApi() {
try {
let response = await window.http.get(`/user/my/newsletters`)
this.newsletters = response.data.results.data;
} catch (error) {
if (error.response) {
throw new Error(error.response.data.message);
}
throw new Error(error.message);
}
},
formatDate: function (value) {
if (!value) return ''
return moment.unix(value).format('LLL');
}
},
template: `
<div class="green card" @click="openModal" style="cursor: pointer">
<div class="content">
<a class="ui green right ribbon label">Newsletter</a>
<div class="header">List Newsletters</div>
<div class="description">
Display all your newsletters
</div>
</div>
</div>
<!-- Modal AccountNewsletter -->
<div class="ui small modal" id="modalNewsletterList">
<i class="close icon"></i>
<div class="header">
My Newsletter List
</div>
<div class="content">
<table class="ui celled table" id="account_newsletters_table">
<thead>
<tr>
<th>Newsletter ID</th>
<th>Name</th>
<th>Role</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody v-if="newsletters != null">
<tr v-for="n in newsletters">
<td>{{ n.id.split('@')[0] }}</td>
<td>{{ n.thread_metadata.name.text }}</td>
<td>{{ n.viewer_metadata.role }}</td>
<td>{{ formatDate(n.thread_metadata.creation_time) }}</td>
<td>
<button class="ui red tiny button" @click="handleUnfollowNewsletter(n.id)">Unfollow</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
`
}

25
src/views/components/SendAudio.js

@ -1,15 +1,20 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'Send', name: 'Send',
components: {
FormRecipient
},
data() { data() {
return { return {
phone: '', phone: '',
type: 'user',
type: window.TYPEUSER,
loading: false, loading: false,
} }
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -49,7 +54,7 @@ export default {
}, },
handleReset() { handleReset() {
this.phone = ''; this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_audio").val(''); $("#file_audio").val('');
}, },
}, },
@ -72,19 +77,7 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field" style="padding-bottom: 30px"> <div class="field" style="padding-bottom: 30px">
<label>Audio</label> <label>Audio</label>
<input type="file" style="display: none" accept="audio/*" id="file_audio"/> <input type="file" style="display: none" accept="audio/*" id="file_audio"/>

26
src/views/components/SendContact.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendContact', name: 'SendContact',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
card_name: '', card_name: '',
card_phone: '', card_phone: '',
@ -11,7 +16,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -58,7 +63,7 @@ export default {
this.phone = ''; this.phone = '';
this.card_name = ''; this.card_name = '';
this.card_phone = ''; this.card_phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
}, },
}, },
template: ` template: `
@ -80,19 +85,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Contact Name</label> <label>Contact Name</label>
<input v-model="card_name" type="text" placeholder="Please enter contact name" <input v-model="card_name" type="text" placeholder="Please enter contact name"

26
src/views/components/SendFile.js

@ -1,5 +1,10 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendFile', name: 'SendFile',
components: {
FormRecipient
},
props: { props: {
maxFileSize: { maxFileSize: {
type: String, type: String,
@ -9,14 +14,14 @@ export default {
data() { data() {
return { return {
caption: '', caption: '',
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
loading: false, loading: false,
} }
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -58,7 +63,7 @@ export default {
handleReset() { handleReset() {
this.caption = ''; this.caption = '';
this.phone = ''; this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_file").val(''); $("#file_file").val('');
}, },
}, },
@ -82,19 +87,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<textarea v-model="caption" placeholder="Type some caption (optional)..." <textarea v-model="caption" placeholder="Type some caption (optional)..."

26
src/views/components/SendImage.js

@ -1,19 +1,24 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendImage', name: 'SendImage',
components: {
FormRecipient
},
data() { data() {
return { return {
phone: '', phone: '',
view_once: false, view_once: false,
compress: false, compress: false,
caption: '', caption: '',
type: 'user',
type: window.TYPEUSER,
loading: false, loading: false,
selected_file: null selected_file: null
} }
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -60,7 +65,7 @@ export default {
this.compress = false; this.compress = false;
this.phone = ''; this.phone = '';
this.caption = ''; this.caption = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_image").val(''); $("#file_image").val('');
}, },
}, },
@ -85,19 +90,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<textarea v-model="caption" type="text" placeholder="Hello this is image caption" <textarea v-model="caption" type="text" placeholder="Hello this is image caption"

26
src/views/components/SendLocation.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendLocation', name: 'SendLocation',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
latitude: '', latitude: '',
longitude: '', longitude: '',
@ -11,7 +16,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -56,7 +61,7 @@ export default {
this.phone = ''; this.phone = '';
this.latitude = ''; this.latitude = '';
this.longitude = ''; this.longitude = '';
this.type = 'user';
this.type = window.TYPEUSER;
}, },
}, },
template: ` template: `
@ -78,19 +83,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Location Latitude</label> <label>Location Latitude</label>
<input v-model="latitude" type="text" placeholder="Please enter latitude" <input v-model="latitude" type="text" placeholder="Please enter latitude"

25
src/views/components/SendMessage.js

@ -1,8 +1,13 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendMessage', name: 'SendMessage',
components: {
FormRecipient
},
data() { data() {
return { return {
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
text: '', text: '',
reply_message_id: '', reply_message_id: '',
@ -11,7 +16,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -57,7 +62,7 @@ export default {
handleReset() { handleReset() {
this.phone = ''; this.phone = '';
this.text = ''; this.text = '';
this.type = 'user';
this.type = window.TYPEUSER;
this.reply_message_id = ''; this.reply_message_id = '';
}, },
}, },
@ -80,19 +85,7 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Reply Message ID</label> <label>Reply Message ID</label>
<input v-model="reply_message_id" type="text" <input v-model="reply_message_id" type="text"

26
src/views/components/SendPoll.js

@ -1,10 +1,15 @@
// export Vue Component // export Vue Component
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendPoll', name: 'SendPoll',
components: {
FormRecipient
},
data() { data() {
return { return {
phone: '', phone: '',
type: 'user',
type: window.TYPEUSER,
loading: false, loading: false,
question: '', question: '',
options: ['', ''], options: ['', ''],
@ -13,7 +18,7 @@ export default {
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -56,7 +61,7 @@ export default {
}, },
handleReset() { handleReset() {
this.phone = ''; this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
this.question = ''; this.question = '';
this.options = ['', '']; this.options = ['', ''];
this.max_vote = 1; this.max_vote = 1;
@ -87,19 +92,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Question</label> <label>Question</label>
<input v-model="question" type="text" placeholder="Please enter question" <input v-model="question" type="text" placeholder="Please enter question"

26
src/views/components/SendVideo.js

@ -1,5 +1,10 @@
import FormRecipient from "./generic/FormRecipient.js";
export default { export default {
name: 'SendVideo', name: 'SendVideo',
components: {
FormRecipient
},
// define props // define props
props: { props: {
maxVideoSize: { maxVideoSize: {
@ -12,14 +17,14 @@ export default {
caption: '', caption: '',
view_once: false, view_once: false,
compress: false, compress: false,
type: 'user',
type: window.TYPEUSER,
phone: '', phone: '',
loading: false, loading: false,
} }
}, },
computed: { computed: {
phone_id() { phone_id() {
return this.type === 'user' ? `${this.phone}@${window.TYPEUSER}` : `${this.phone}@${window.TYPEGROUP}`
return this.phone + this.type;
} }
}, },
methods: { methods: {
@ -65,7 +70,7 @@ export default {
this.view_once = false; this.view_once = false;
this.compress = false; this.compress = false;
this.phone = ''; this.phone = '';
this.type = 'user';
this.type = window.TYPEUSER;
$("#file_video").val(''); $("#file_video").val('');
}, },
}, },
@ -91,19 +96,8 @@ export default {
</div> </div>
<div class="content"> <div class="content">
<form class="ui form"> <form class="ui form">
<div class="field">
<label>Type</label>
<select name="type" v-model="type" aria-label="type">
<option value="group">Group Message</option>
<option value="user">Private Message</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input v-model="phone" type="text" placeholder="6289..."
aria-label="phone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
<FormRecipient v-model:type="type" v-model:phone="phone"/>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<textarea v-model="caption" placeholder="Type some caption (optional)..." <textarea v-model="caption" placeholder="Type some caption (optional)..."

52
src/views/components/generic/FormRecipient.js

@ -0,0 +1,52 @@
export default {
name: 'FormRecipient',
props: {
type: {
type: String,
required: true
},
phone: {
type: String,
required: true
},
},
data() {
return {
recipientTypes: []
};
},
computed: {
phone_id() {
return this.phone + this.type;
}
},
mounted() {
this.recipientTypes = [
{ value: window.TYPEUSER, text: 'Private Message' },
{ value: window.TYPEGROUP, text: 'Group Message' },
{ value: window.TYPENEWSLETTER, text: 'Newsletter' }
];
},
methods: {
updateType(event) {
this.$emit('update:type', event.target.value);
},
updatePhone(event) {
this.$emit('update:phone', event.target.value);
}
},
template: `
<div class="field">
<label>Type</label>
<select name="type" @change="updateType" class="ui dropdown">
<option v-for="type in recipientTypes" :value="type.value">{{ type.text }}</option>
</select>
</div>
<div class="field">
<label>Phone / Group ID</label>
<input :value="phone" aria-label="wa identifier" @input="updatePhone">
<input :value="phone_id" disabled aria-label="whatsapp_id">
</div>
`
}

17
src/views/index.html

@ -86,6 +86,14 @@
<group-add-participants></group-add-participants> <group-add-participants></group-add-participants>
</div> </div>
<div class="ui horizontal divider">
Newsletter
</div>
<div class="ui three column doubling grid cards">
<newsletter-list></newsletter-list>
</div>
<div class="ui horizontal divider"> <div class="ui horizontal divider">
Account Account
</div> </div>
@ -98,8 +106,9 @@
</div> </div>
<script> <script>
window.TYPEGROUP = "g.us";
window.TYPEUSER = "s.whatsapp.net";
window.TYPEGROUP = "@g.us";
window.TYPEUSER = "@s.whatsapp.net";
window.TYPENEWSLETTER = "@newsletter";
window.showErrorInfo = (message) => { window.showErrorInfo = (message) => {
$('body').toast({ $('body').toast({
position: 'bottom right', position: 'bottom right',
@ -120,7 +129,7 @@
} }
window.http = axios.create({ window.http = axios.create({
baseURL: `${window.location.protocol}//${window.location.hostname}`
baseURL: `${window.location.protocol}//${window.location.hostname}${window.location.port ? ':' + window.location.port : ''}`
}); });
{{ if isEnableBasicAuth .BasicAuthToken }} {{ if isEnableBasicAuth .BasicAuthToken }}
window.http.defaults.headers.common['Authorization'] = {{ .BasicAuthToken }}; window.http.defaults.headers.common['Authorization'] = {{ .BasicAuthToken }};
@ -150,6 +159,7 @@
import AccountAvatar from "./components/AccountAvatar.js"; import AccountAvatar from "./components/AccountAvatar.js";
import AccountUserInfo from "./components/AccountUserInfo.js"; import AccountUserInfo from "./components/AccountUserInfo.js";
import AccountPrivacy from "./components/AccountPrivacy.js"; import AccountPrivacy from "./components/AccountPrivacy.js";
import NewsletterList from "./components/NewsletterList.js";
const showErrorInfo = (message) => { const showErrorInfo = (message) => {
$('body').toast({ $('body').toast({
@ -176,6 +186,7 @@
SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll, SendMessage, SendImage, SendFile, SendVideo, SendContact, SendLocation, SendAudio, SendPoll,
MessageDelete, MessageUpdate, MessageReact, MessageRevoke, MessageDelete, MessageUpdate, MessageReact, MessageRevoke,
GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants, GroupList, GroupCreate, GroupJoinWithLink, GroupAddParticipants,
NewsletterList,
AccountAvatar, AccountUserInfo, AccountPrivacy AccountAvatar, AccountUserInfo, AccountPrivacy
}, },
delimiters: ['[[', ']]'], delimiters: ['[[', ']]'],

Loading…
Cancel
Save