Skip to content

Commit

Permalink
Merge branch 'master' into robin/biome-correctness-rules
Browse files Browse the repository at this point in the history
  • Loading branch information
robines committed Dec 16, 2024
2 parents 5541106 + 3f5e652 commit 42f126c
Show file tree
Hide file tree
Showing 30 changed files with 453 additions and 115 deletions.
1 change: 1 addition & 0 deletions backend/root/utils/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@
samfundet__login = 'samfundet:login'
samfundet__register = 'samfundet:register'
samfundet__logout = 'samfundet:logout'
samfundet__change_password = 'samfundet:change-password'
samfundet__user = 'samfundet:user'
samfundet__groups = 'samfundet:groups'
samfundet__users = 'samfundet:users'
Expand Down
17 changes: 17 additions & 0 deletions backend/samfundet/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from django.core.exceptions import ValidationError
from django.core.files.images import ImageFile
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.password_validation import validate_password

from root.constants import PHONE_NUMBER_REGEX
from root.utils.mixins import CustomBaseSerializer
Expand Down Expand Up @@ -235,6 +236,22 @@ class Meta:
fields = '__all__'


class ChangePasswordSerializer(serializers.Serializer):
current_password = serializers.CharField(required=True)
new_password = serializers.CharField(required=True)

def validate_current_password(self, value: str) -> str:
user = self.context['request'].user
if not user.check_password(value):
raise serializers.ValidationError('Incorrect current password')
return value

def validate_new_password(self, value: str) -> str:
user = self.context['request'].user
validate_password(value, user)
return value


class LoginSerializer(serializers.Serializer):
"""
This serializer defines two fields for authentication:
Expand Down
1 change: 1 addition & 0 deletions backend/samfundet/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
path('login/', views.LoginView.as_view(), name='login'),
path('register/', views.RegisterView.as_view(), name='register'),
path('logout/', views.LogoutView.as_view(), name='logout'),
path('password/change/', views.ChangePasswordView.as_view(), name='change-password'),
path('user/', views.UserView.as_view(), name='user'),
path('groups/', views.AllGroupsView.as_view(), name='groups'),
path('users/', views.AllUsersView.as_view(), name='users'),
Expand Down
17 changes: 16 additions & 1 deletion backend/samfundet/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
from django.core.mail import EmailMessage
from django.db.models import Q, Count, QuerySet
from django.shortcuts import get_object_or_404
from django.contrib.auth import login, logout
from django.contrib.auth import login, logout, update_session_auth_hash
from django.utils.encoding import force_bytes
from django.middleware.csrf import get_token
from django.utils.decorators import method_decorator
Expand Down Expand Up @@ -76,6 +76,7 @@
UserFeedbackSerializer,
UserGangRoleSerializer,
InterviewRoomSerializer,
ChangePasswordSerializer,
FoodPreferenceSerializer,
UserPreferenceSerializer,
InformationPageSerializer,
Expand Down Expand Up @@ -481,6 +482,20 @@ def post(self, request: Request) -> Response:
return res


class ChangePasswordView(APIView):
permission_classes = (IsAuthenticated,)

def post(self, request: Request) -> Response:
serializer = ChangePasswordSerializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
new_password = serializer.validated_data['new_password']
user = request.user
user.set_password(new_password)
user.save()
update_session_auth_hash(request, user)
return Response({'message': 'Successfully updated password'}, status=status.HTTP_200_OK)


class UserView(APIView):
permission_classes = [IsAuthenticated]

Expand Down
17 changes: 15 additions & 2 deletions frontend/src/Components/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
justify-content: center;

// Remove underline when button is a link
&, &:hover {
&,
&:hover {
text-decoration: none;
}

Expand All @@ -37,6 +38,19 @@
color: black;
}

.button_selected {
background-color: white;
color: black;
box-shadow: inset 0 0 1.5px 2px rgba(0, 0, 0, 0.15);
cursor: default;

// Override hover effects for selected buttons
&:hover:not([disabled]) {
filter: none;
box-shadow: inset 0 0 1.5px 2px rgba(0, 0, 0, 0.15);
}
}

.button_samf {
background-color: $red-samf;
color: white;
Expand Down Expand Up @@ -169,4 +183,3 @@
text-decoration: underline;
}
}

1 change: 1 addition & 0 deletions frontend/src/Components/Button/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import styles from './Button.module.scss';

export const themeToStyleMap = {
basic: styles.button_basic,
selected: styles.button_selected,
pure: styles.pure,
text: styles.button_text,
samf: styles.button_samf,
Expand Down
90 changes: 43 additions & 47 deletions frontend/src/Components/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Icon } from '@iconify/react';
import classNames from 'classnames';
import { format } from 'date-fns';
import { useMemo } from 'react';
import { forwardRef, useMemo } from 'react';
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { MiniCalendar } from '~/Components';
Expand All @@ -21,56 +21,52 @@ type DatePickerProps = {
maxDate?: Date;
};

export function DatePicker({
value: initialValue,
onChange,
disabled,
label,
buttonClassName,
minDate,
maxDate,
}: DatePickerProps) {
const isControlled = initialValue !== undefined;
export const DatePicker = forwardRef<HTMLButtonElement, DatePickerProps>(
({ value: initialValue, onChange, disabled, label, buttonClassName, minDate, maxDate }, ref) => {
const isControlled = initialValue !== undefined;

const [date, setDate] = useState<Date | null>(null);
const [open, setOpen] = useState(false);
const [date, setDate] = useState<Date | null>(null);
const [open, setOpen] = useState(false);

const { t } = useTranslation();
const { t } = useTranslation();

const clickOutsideRef = useClickOutside<HTMLDivElement>(() => setOpen(false));
const clickOutsideRef = useClickOutside<HTMLDivElement>(() => setOpen(false));

const value = useMemo(() => {
if (isControlled) {
return initialValue;
}
return date;
}, [isControlled, initialValue, date]);
const value = useMemo(() => {
if (isControlled) {
return initialValue;
}
return date;
}, [isControlled, initialValue, date]);

function handleChange(d: Date | null) {
setDate(d);
onChange?.(d);
}
function handleChange(d: Date | null) {
setDate(d);
onChange?.(d);
}

return (
<div className={styles.container} ref={clickOutsideRef}>
<button
type="button"
className={classNames(styles.button, buttonClassName)}
onClick={() => setOpen((v) => !v)}
disabled={disabled}
>
<Icon icon="material-symbols:calendar-month-outline-rounded" />
{value ? format(value, 'PPP') : <span>{label ?? t(KEY.pick_a_date)}</span>}
</button>
<div className={classNames(styles.popover, !open && styles.hidden)}>
<MiniCalendar
baseDate={value || new Date()}
onChange={handleChange}
minDate={minDate}
maxDate={maxDate}
displayLabel
/>
return (
<div className={styles.container} ref={clickOutsideRef}>
<button
type="button"
className={classNames(styles.button, buttonClassName)}
onClick={() => setOpen((v) => !v)}
disabled={disabled}
ref={ref}
>
<Icon icon="material-symbols:calendar-month-outline-rounded" />
{value ? format(value, 'PPP') : <span>{label ?? t(KEY.pick_a_date)}</span>}
</button>
<div className={classNames(styles.popover, !open && styles.hidden)}>
<MiniCalendar
baseDate={value || new Date()}
onChange={handleChange}
minDate={minDate}
maxDate={maxDate}
displayLabel
/>
</div>
</div>
</div>
);
}
);
},
);
DatePicker.displayName = 'DatePicker';
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

@import 'src/mixins';


.container {
width: 100%;
border: 2px solid $grey-3;
overflow: hidden;

@include theme-dark{
@include theme-dark {
border: 1px solid $grey-0;
}
}

.parent {
border-radius: 10px;
border-color: $grey-4;
background-color: $grey_4;
background-color: $grey-4;

@include theme-dark{
@include theme-dark {
background-color: #1d0809;
border-color: transparent;
}
Expand All @@ -30,7 +29,7 @@
margin: 10px;
width: auto;

@include theme-dark{
@include theme-dark {
border-color: transparent;
}
}
Expand All @@ -51,16 +50,16 @@
text-align: left;
width: 100%;

@include theme-dark{
@include theme-dark {
background-color: #1d0809;
color: white;
}
}

.extendable_header_wrapper:hover{
.extendable_header_wrapper:hover {
background-color: #dbdbdb;

@include theme-dark{
@include theme-dark {
background-color: #1d0809;
}
}
Expand Down
26 changes: 13 additions & 13 deletions frontend/src/Components/Input/Input.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

@import 'src/mixins';

.input[type="date"],
.input[type="datetime-local"],
.input[type="email"],
.input[type="month"],
.input[type="number"],
.input[type="password"],
.input[type="search"],
.input[type="tel"],
.input[type="text"],
.input[type="time"],
.input[type="url"],
.input[type="week"] {
.date,
.datetimeLocal,
.email,
.month,
.number,
.password,
.search,
.tel,
.text,
.time,
.url,
.week {
@include rounded-lighter;
padding: 0.75rem 1rem;
border: 1px solid $grey-35;
Expand Down Expand Up @@ -44,7 +44,7 @@

/* stylelint-disable no-descending-specificity */
// Hide up and down buttons unless hover/focus
.input[type="number"] {
.number {
-webkit-appearance: textfield;

&:hover, &:focus {
Expand Down
24 changes: 23 additions & 1 deletion frontend/src/Components/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,28 @@ import styles from './Input.module.scss';
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}

export const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, ...props }, ref) => {
return <input ref={ref} type={type} className={classNames(styles.input, className)} {...props} />;
const classMap: Record<string, string> = {
date: styles.date,
'datetime-local': styles.datetimeLocal,
email: styles.email,
month: styles.month,
number: styles.number,
password: styles.password,
search: styles.search,
tel: styles.tel,
text: styles.text,
time: styles.time,
url: styles.url,
week: styles.week,
} as const;

return (
<input
ref={ref}
type={type}
className={classNames(styles.input, type ? classMap[type] : '', className)}
{...props}
/>
);
});
Input.displayName = 'Input';
4 changes: 3 additions & 1 deletion frontend/src/Components/NumberInput/NumberInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
event.preventDefault();
const add = event.key === 'ArrowUp' ? 1 : -1;
const newVal = clampValue((Number(inputValue) || 0) + add);
const multiplier = event.shiftKey ? 10 : 1;

const newVal = clampValue((Number(inputValue) || 0) + add * multiplier);
if (!Number.isNaN(newVal)) {
setInputValue(newVal);
onChange?.(newVal);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function GangSeparatePositions({ recruitmentSeparatePositions }: GangSepa
return (
<ExpandableHeader
showByDefault={true}
label={t(KEY.recruitment_gangs_with_separate_positions)}
label={t(KEY.recruitment_positions_with_separate_recruitment)}
className={styles.separate_header}
>
{recruitmentSeparatePositions.map((pos) => (
Expand Down
Loading

0 comments on commit 42f126c

Please sign in to comment.