Fakultas Ilmu Komputer UI
Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
PMPL
Class Project
Kape
Commits
d846a0a6
Commit
d846a0a6
authored
Oct 06, 2019
by
Nabila Fakhirah
Browse files
Fixed merge conflict to merge with master
parents
2f41ddcb
1682b6a7
Pipeline
#22008
passed with stages
in 25 minutes and 14 seconds
Changes
49
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
.DS_Store
0 → 100644
View file @
d846a0a6
File added
.gitlab/CODEOWNERS
0 → 100644
View file @
d846a0a6
# Code owners file
## Changes to these file(s) require approval from the teaching team
sonar-project.properties @addianto @hafiyyan94
\ No newline at end of file
README.md
View file @
d846a0a6
...
...
@@ -2,6 +2,13 @@
> Internship matchmaking platform for students and companies.
[

](https://gitlab.cs.ui.ac.id/pmpl/class-project/kape/commits/master)
[

](https://gitlab.cs.ui.ac.id/pmpl/class-project/kape/commits/master)
[

](https://pmpl.cs.ui.ac.id/sonarqube/dashboard?id=id.ac.ui.cs.foss%3Akape)
[

](https://pmpl.cs.ui.ac.id/sonarqube/dashboard?id=id.ac.ui.cs.foss%3Akape)
[

](https://pmpl.cs.ui.ac.id/sonarqube/dashboard?id=id.ac.ui.cs.foss%3Akape)
## Table of Contents
-
[
Install
](
#install
)
...
...
assets/js/CompanyProfile.jsx
View file @
d846a0a6
...
...
@@ -21,7 +21,7 @@ export default class CompanyProfile extends React.Component {
<
div
className
=
"biodataCompany"
>
<
h2
>
{
data
.
name
}
</
h2
>
<
h3
>
{
data
.
address
}
t
</
h3
>
<
p
>
{
data
.
description
}
</
p
>
<
p
>
{
data
.
category
}
-
{
data
.
description
}
</
p
>
</
div
>
</
Container
>
</
Segment
>
...
...
assets/js/ProfilePage.jsx
View file @
d846a0a6
...
...
@@ -23,23 +23,28 @@ export default class ProfilePage extends React.Component {
major
:
''
,
batch
:
''
,
email
:
''
,
region
:
''
,
cityOfBirth
:
''
,
dateOfBirth
:
''
,
resume
:
''
,
phone_number
:
''
,
show_transcript
:
''
,
photo
:
''
,
intro
:
''
,
form
:
{
picture
:
''
,
email
:
''
,
phone_number
:
''
,
region
:
''
,
resume
:
''
,
show_transcript
:
''
,
intro
:
''
,
},
bagikanTranskrip
:
''
,
acceptedNo
:
0
,
refresh
:
1
,
loading
:
false
,
linkedin_url
:
''
,
self_description
:
''
,
};
this
.
getProfile
=
this
.
getProfile
.
bind
(
this
);
...
...
@@ -64,6 +69,7 @@ export default class ProfilePage extends React.Component {
major
:
data
.
major
,
batch
:
data
.
batch
,
email
:
data
.
user
.
email
,
region
:
data
.
region
,
cityOfBirth
:
data
.
birth_place
,
dateOfBirth
:
data
.
birth_date
,
phone_number
:
data
.
phone_number
,
...
...
@@ -72,6 +78,8 @@ export default class ProfilePage extends React.Component {
acceptedNo
:
data
.
accepted_no
,
bagikanTranskrip
:
data
.
show_transcript
,
refresh
:
this
.
state
.
refresh
+
1
,
intro
:
data
.
intro
,
linkedin_url
:
data
.
linkedin_url
,
self_description
:
data
.
self_description
,
});
if
(
this
.
props
.
route
.
own
)
{
...
...
@@ -158,12 +166,25 @@ export default class ProfilePage extends React.Component {
</
Form
.
Field
>
<
Form
.
Field
>
<
label
htmlFor
=
"self_description"
>
Deskripsi Diri
</
label
>
<
input
onChange
=
{
this
.
handleChange
}
placeholder
=
"Saya s
enang
belajar"
name
=
"self_description"
/>
<
input
onChange
=
{
this
.
handleChange
}
placeholder
=
"Saya s
uka
belajar"
name
=
"self_description"
/>
</
Form
.
Field
>
<
Form
.
Field
>
<
label
htmlFor
=
"linkedin_url"
>
URL Profile LinkedIn
</
label
>
<
input
onChange
=
{
this
.
handleChange
}
placeholder
=
"https://www.linkedin.com/in/jojo/"
name
=
"linkedin_url"
/>
</
Form
.
Field
>
<
Form
.
Field
>
<
label
htmlFor
=
"region"
>
Region
</
label
>
<
input
onChange
=
{
this
.
handleChange
}
placeholder
=
"Indonesia"
name
=
"region"
/>
</
Form
.
Field
>
<
Form
.
Field
>
<
label
htmlFor
=
"resume"
>
Resume
</
label
>
<
input
onChange
=
{
this
.
handleFile
}
placeholder
=
"Resume"
name
=
"resume"
type
=
"File"
/>
</
Form
.
Field
>
<
Form
.
Field
>
<
label
htmlFor
=
"intro"
>
Intro
</
label
>
<
input
onChange
=
{
this
.
handleChange
}
placeholder
=
"Ceritakan dirimu secara singkat"
name
=
"intro"
/>
</
Form
.
Field
>
<
Form
.
Field
>
<
Checkbox
onChange
=
{
this
.
handleCheckbox
}
...
...
@@ -256,8 +277,44 @@ export default class ProfilePage extends React.Component {
</
Grid
.
Column
>
</
Grid
>
</
Segment
>
<
Segment
basic
vertical
>
<
Grid
>
<
Grid
.
Column
width
=
{
2
}
>
<
Icon
name
=
"linkedin"
size
=
"big"
/>
</
Grid
.
Column
>
<
Grid
.
Column
width
=
{
13
}
>
<
a
href
=
{
this
.
state
.
linkedin_url
}
>
{
this
.
state
.
linkedin_url
||
'
N/A
'
}
</
a
>
</
Grid
.
Column
>
</
Grid
>
</
Segment
>
</
div
>
<
Segment
basic
vertical
>
<
Grid
>
<
Grid
.
Column
width
=
{
2
}
>
<
Icon
name
=
"map pin"
size
=
"big"
/>
</
Grid
.
Column
>
<
Grid
.
Column
width
=
{
13
}
>
<
p
>
{
this
.
state
.
region
||
'
N/A
'
}
</
p
>
</
Grid
.
Column
>
</
Grid
>
</
Segment
>
<
Segment
basic
vertical
>
<
Grid
>
<
Grid
.
Column
width
=
{
2
}
>
<
h3
>
Intro
</
h3
>
</
Grid
.
Column
>
<
Grid
.
Column
width
=
{
13
}
>
<
p
>
{
this
.
state
.
intro
||
'
N/A
'
}
</
p
>
</
Grid
.
Column
>
</
Grid
>
</
Segment
>
<
Container
textAlign
=
"center"
>
<
div
className
=
"buttonProfile"
>
<
Button
onClick
=
{
this
.
gotoStudentResume
}
disabled
=
{
!
this
.
state
.
resume
}
primary
size
=
"small"
>
Resume
</
Button
>
...
...
assets/js/SupervisorPage.jsx
View file @
d846a0a6
...
...
@@ -7,6 +7,7 @@ import ApplicationList from './components/ApplicationList';
const
cols
=
[
{
key
:
'
StudentName
'
,
label
:
'
Nama
'
},
{
key
:
'
StudentID
'
,
label
:
'
NPM
'
},
{
key
:
'
Major
'
,
label
:
'
Major
'
},
{
key
:
'
Perusahaan
'
,
label
:
'
Perusahaan
'
},
{
key
:
'
Posisi
'
,
label
:
'
Posisi
'
},
{
key
:
'
Status
'
,
label
:
'
Status
'
},
...
...
assets/js/__test__/CompanyProfile-test.jsx
View file @
d846a0a6
...
...
@@ -24,6 +24,7 @@ const companyUser = {
verified
:
true
,
logo
:
'
http://localhost:8001/files/company-logo/8a258a48-3bce-4873-b5d1-538b360d0059.png
'
,
address
:
'
Jl. Kebayoran Baru nomor 13, Jakarta Barat
'
,
category
:
'
Belum ada kategori perusahaan
'
,
},
supervisor
:
null
,
student
:
null
,
...
...
assets/js/__test__/ProfilePage-test.jsx
View file @
d846a0a6
...
...
@@ -139,7 +139,7 @@ describe('ProfilePage', () => {
const
profile
=
ReactTestUtils
.
renderIntoDocument
(
<
ProfilePage
route
=
{
{
own
:
true
,
data
:
studentSession
}
}
user
=
{
{
data
:
studentSession
}
}
params
=
{
{
id
:
3
}
}
/>);
const
checkboxNode
=
ReactTestUtils
.
scryRenderedDOMComponentsWithTag
(
profile
,
'
Input
'
)[
5
];
const
checkboxNode
=
ReactTestUtils
.
scryRenderedDOMComponentsWithTag
(
profile
,
'
Input
'
)[
7
];
const
checkbox
=
false
;
checkboxNode
.
value
=
checkbox
;
profile
.
getProfile
().
then
(()
=>
expect
(
profile
.
state
.
show_transcript
).
to
.
equal
(
true
));
...
...
@@ -213,4 +213,4 @@ describe('ProfilePage', () => {
});
});
});
\ No newline at end of file
assets/js/__test__/components/CompanyRegisterModal-test.jsx
0 → 100644
View file @
d846a0a6
import
React
from
'
react
'
;
import
ReactTestUtils
from
'
react-addons-test-utils
'
;
import
CompanyRegisterModal
from
'
../../components/CompanyRegisterModal
'
;
describe
(
'
CompanyRegisterModal
'
,
()
=>
{
function
validatePassword
(
password
)
{
const
lowerCaseLetters
=
/
[
a-z
]
/g
;
const
upperCaseLetters
=
/
[
A-Z
]
/g
;
const
numbers
=
/
[
0-9
]
/g
;
if
(
password
.
length
<
8
)
return
"
Password less than 8
"
;
else
if
(
!
lowerCaseLetters
.
test
(
password
))
return
"
Password at least one lowercase letter
"
;
else
if
(
!
upperCaseLetters
.
test
(
password
))
return
"
Password at least one uppercase letter
"
;
else
if
(
!
numbers
.
test
(
password
))
return
"
Password at least one number
"
;
else
return
true
}
it
(
'
renders without problem
'
,
()
=>
{
const
companyRegister
=
ReactTestUtils
.
renderIntoDocument
(
<
CompanyRegisterModal
/>);
expect
(
companyRegister
).
to
.
exist
;
});
it
(
'
handle password validation
'
,
()
=>
{
const
password
=
'
3s24Aasd
'
;
expect
(
validatePassword
(
password
)).
to
.
equal
(
true
);
});
});
assets/js/components/ApplicationList.jsx
View file @
d846a0a6
...
...
@@ -23,9 +23,10 @@ export default class ApplicationList extends React.Component {
generateRows
()
{
return
this
.
props
.
items
.
map
(
item
=>
(
<
Table
.
Row
key
=
{
`
${
item
.
npm
}
_
${
item
.
company
}
_
${
item
.
position
}
_
${
item
.
status
}
_row`
}
>
<
Table
.
Row
key
=
{
`
${
item
.
npm
}
_
${
item
.
major
}
_
${
item
.
company
}
_
${
item
.
position
}
_
${
item
.
status
}
_row`
}
>
<
Table
.
Cell
key
=
{
`
${
item
.
name
}
_name`
}
>
{
item
.
name
}
</
Table
.
Cell
>
<
Table
.
Cell
key
=
{
`
${
item
.
name
}
_npm`
}
>
{
item
.
npm
}
</
Table
.
Cell
>
<
Table
.
Cell
key
=
{
`
${
item
.
name
}
_major`
}
>
{
item
.
major
}
</
Table
.
Cell
>
<
Table
.
Cell
key
=
{
`
${
item
.
name
}
_company`
}
>
{
item
.
company_name
}
</
Table
.
Cell
>
...
...
assets/js/components/CompanyRegisterModal.jsx
View file @
d846a0a6
import
React
from
'
react
'
;
import
{
browserHistory
}
from
'
react-router
'
;
import
{
Modal
,
Button
,
Form
,
Input
,
TextArea
,
Header
,
Icon
}
from
'
semantic-ui-react
'
;
import
{
Modal
,
Button
,
Form
,
Input
,
TextArea
,
Header
,
Icon
,
Dropdown
}
from
'
semantic-ui-react
'
;
import
ModalAlert
from
'
./../components/ModalAlert
'
;
import
Server
from
'
./../lib/Server
'
;
import
Storage
from
'
./../lib/Storage
'
;
...
...
@@ -17,6 +17,26 @@ export default class CompanyRegisterModal extends React.Component {
this
.
handleSubmit
=
this
.
handleSubmit
.
bind
(
this
);
}
categoryOptions
=
[
{
value
:
'
Perusahaan Asuransi
'
,
text
:
'
Perusahaan Asuransi
'
},
{
value
:
'
Perusahaan Bioteknologi
'
,
text
:
'
Perusahaan Bioteknologi
'
},
{
value
:
'
Perusahaan Elektronik
'
,
text
:
'
Perusahaan Elektronik
'
},
{
value
:
'
Perusahaan Energi
'
,
text
:
'
Perusahaan Energi
'
},
{
value
:
'
Perusahaan Farmasi
'
,
text
:
'
Perusahaan Farmasi
'
},
{
value
:
'
Firma Akuntansi
'
,
text
:
'
Firma Akuntansi
'
},
{
value
:
'
Agen Periklanan
'
,
text
:
'
Agen Periklanan
'
},
{
value
:
'
Perusahaan Internet
'
,
text
:
'
Perusahaan Internet
'
},
{
value
:
'
Perusahaan Jasa
'
,
text
:
'
Perusahaan Jasa
'
},
{
value
:
'
Perusahaan Komputer
'
,
text
:
'
Perusahaan Komputer
'
},
{
value
:
'
Perusahaan Konsultansi
'
,
text
:
'
Perusahaan Konsultansi
'
},
{
value
:
'
Perusahaan Logistik
'
,
text
:
'
Perusahaan Logistik
'
},
{
value
:
'
Perusahaan Media
'
,
text
:
'
Perusahaan Media
'
},
{
value
:
'
Perusahaan Perangkat Keras
'
,
text
:
'
Perusahaan Perangkat Keras
'
},
{
value
:
'
Perusahaan Perangkat Lunak
'
,
text
:
'
Perusahaan Perangkat Lunak
'
},
{
value
:
'
Perusahaan Teknologi
'
,
text
:
'
Perusahaan Teknologi
'
},
{
value
:
'
Perusahaan Telekomunikasi
'
,
text
:
'
Perusahaan Telekomunikasi
'
},
]
componentWillUpdate
()
{
this
.
fixBody
();
}
...
...
@@ -34,6 +54,10 @@ export default class CompanyRegisterModal extends React.Component {
this
.
setState
({
[
e
.
target
.
name
]:
e
.
target
.
value
});
};
handleSelectChange
=
(
e
,
data
)
=>
{
this
.
setState
({
[
data
.
name
]:
data
.
value
});
}
handleFile
=
(
e
)
=>
{
this
.
setState
({
[
e
.
target
.
name
]:
e
.
target
.
files
[
0
]
});
};
...
...
@@ -42,6 +66,9 @@ export default class CompanyRegisterModal extends React.Component {
handlePassword
=
(
e
)
=>
{
if
(
e
.
target
.
name
===
'
password
'
)
this
.
passwordField
=
e
.
target
;
else
if
(
e
.
target
.
name
===
'
password-confirm
'
)
this
.
passwordConfirmField
=
e
.
target
;
const
validatePassword
=
/
(?=
.*
\d)(?=
.*
[
a-z
])(?=
.*
[
A-Z
])
.
{8,}
/g
;
if
(
!
validatePassword
.
test
(
this
.
passwordField
.
value
))
this
.
passwordField
.
setCustomValidity
(
"
Must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters
"
);
else
this
.
passwordField
.
setCustomValidity
(
""
);
const
isExist
=
this
.
passwordField
&&
this
.
passwordConfirmField
;
if
(
isExist
)
{
if
(
this
.
passwordField
.
value
!==
this
.
passwordConfirmField
.
value
)
{
...
...
@@ -99,6 +126,19 @@ export default class CompanyRegisterModal extends React.Component {
<
label
htmlFor
=
"name"
>
Nama Perusahaan
</
label
>
<
Input
onChange
=
{
this
.
handleChange
}
placeholder
=
"Nama Perusahaan"
name
=
"name"
required
/>
</
Form
.
Field
>
<
Form
.
Field
required
>
<
label
htmlFor
=
"name"
>
Kategori Perusahaan
</
label
>
<
Dropdown
placeholder
=
'Kategori Perusahaan'
fluid
name
=
"category"
onChange
=
{
this
.
handleSelectChange
}
search
selection
options
=
{
this
.
categoryOptions
}
/>
</
Form
.
Field
>
<
Form
.
Field
required
></
Form
.
Field
>
<
Form
.
Field
required
>
<
label
htmlFor
=
"logo"
>
Logo
</
label
>
<
Input
...
...
core/admin.py
View file @
d846a0a6
from
django.contrib
import
admin
from
core.models.accounts
import
Company
,
Supervisor
,
Student
from
core.models.feedbacks
import
Feedback
from
core.models.vacancies
import
Vacancy
admin
.
site
.
register
(
Company
)
admin
.
site
.
register
(
Student
)
admin
.
site
.
register
(
Supervisor
)
admin
.
site
.
register
(
Vacancy
)
admin
.
site
.
register
(
Feedback
)
core/lib/validators.py
View file @
d846a0a6
import
os
import
math
from
django.core.exceptions
import
ValidationError
from
kape.settings
import
MAX_UPLOAD_SIZE
...
...
@@ -18,3 +19,29 @@ def validate_document_file_extension(value):
def
validate_image_file_extension
(
value
):
validate_file
(
value
,
[
'.jpeg'
,
'.jpg'
,
'.png'
,
'.JPG'
,
'.JPEG'
])
def
validate_npm
(
value
):
'''
NPM UI terdiri dari 10 digit, misalnya: 1234567894. Digit terakhir
(disebut checksum) dapat dihitung dari sembilan digit pertama.
Checksum dipakai untuk deteksi error pada nomor ID seperti NPM.
Digit-digit tersebut diberi indeks: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
sesuai posisinya. Kita asosiasikan digit-digit tersebut dengan
variabel-variabel d[0], d[1], d[2] , ..., d[9].
Checksum C (yaitu d[9]) dihitung dengan rumus sebagai berikut:
1) a = 3* (d[0] + d[2] + d[4] + d[6] + d[8])
2) b = (d[1] + d[3] + d[5] + d[7])
3) C = (a + b) % 7
Sebagai contoh, checksum untuk NPM 1234567894 adalah
C = (3*(1 + 3 + 5 + 7 + 9) + (2 + 4 + 6 + 8)) % 7 = 95 % 7 = 4
yang memang ternyata benar.
Source: Lim Yohanes Stefanus, Drs., M.Math., Ph.D
'''
if
math
.
ceil
(
math
.
log
(
value
+
1
,
10
))
!=
10
:
raise
ValidationError
(
u
"NPM must be 10 digits"
)
val_string
=
str
(
value
)
if
sum
([
3
*
int
(
a
)
for
a
in
val_string
[:
-
1
:
2
]]
+
[
int
(
a
)
for
a
in
val_string
[
1
:
-
1
:
2
]])
%
7
!=
int
(
val_string
[
-
1
]):
raise
ValidationError
(
u
"NPM {} has invalid checksum"
.
format
(
value
))
\ No newline at end of file
core/migrations/0014_auto_20191004_1340.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-10-04 06:40
from
__future__
import
unicode_literals
import
core.lib.validators
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0013_auto_20170602_1130'
),
]
operations
=
[
migrations
.
AlterField
(
model_name
=
'student'
,
name
=
'npm'
,
field
=
models
.
IntegerField
(
unique
=
True
,
validators
=
[
core
.
lib
.
validators
.
validate_npm
]),
),
]
core/migrations/0014_company_category.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-10-05 04:52
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0013_auto_20170602_1130'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'company'
,
name
=
'category'
,
field
=
models
.
CharField
(
default
=
b
'Belum ada kategori perusahaan'
,
max_length
=
140
),
),
]
core/migrations/0014_feedback.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-10-03 13:39
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0013_auto_20170602_1130'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'Feedback'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
auto_created
=
True
,
primary_key
=
True
,
serialize
=
False
,
verbose_name
=
'ID'
)),
(
'created'
,
models
.
DateTimeField
(
auto_now_add
=
True
)),
(
'title'
,
models
.
CharField
(
blank
=
True
,
default
=
b
''
,
max_length
=
100
)),
(
'content'
,
models
.
TextField
()),
],
options
=
{
'ordering'
:
[
'created'
],
},
),
]
core/migrations/0014_student_linkedin_url.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-10-05 08:48
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0013_auto_20170602_1130'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'student'
,
name
=
'linkedin_url'
,
field
=
models
.
URLField
(
blank
=
True
,
null
=
True
),
),
]
core/migrations/0014_student_region.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.11.17 on 2019-10-05 10:07
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0013_auto_20170602_1130'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'student'
,
name
=
'region'
,
field
=
models
.
CharField
(
blank
=
True
,
max_length
=
30
,
null
=
True
),
),
]
core/migrations/0015_merge_20191005_1926.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.10.5 on 2019-10-05 12:26
from
__future__
import
unicode_literals
from
django.db
import
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0014_feedback'
),
(
'core'
,
'0014_company_category'
),
]
operations
=
[
]
core/migrations/0015_merge_20191005_1957.py
0 → 100644
View file @
d846a0a6
# -*- coding: utf-8 -*-
# Generated by Django 1.11.17 on 2019-10-05 12:57
from
__future__
import
unicode_literals
from
django.db
import
migrations
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'core'
,
'0014_feedback'
),
(
'core'
,
'0014_company_category'
),
(
'core'
,
'0014_student_region'
),
]
operations
=
[
]
Prev
1
2
3
Next
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment