Skip to content

Commit

Permalink
Merge branch 'main' into 862-reject_duplicate_submissions
Browse files Browse the repository at this point in the history
 # Conflicts:
 #	kobo/apps/openrosa/libs/utils/logger_tools.py
  • Loading branch information
noliveleger committed Oct 10, 2024
2 parents c416e42 + a77b81f commit b227f5b
Show file tree
Hide file tree
Showing 47 changed files with 1,541 additions and 826 deletions.
5 changes: 5 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
5. [ ] Write a title and, if necessary, a description of your work suitable for publishing in our [release notes](https://community.kobotoolbox.org/tag/release-notes)
6. [ ] Mention any related issues in this repository (as #ISSUE) and in other repositories (as kobotoolbox/other#ISSUE)
7. [ ] Open an issue in the [docs](https://github.com/kobotoolbox/docs/issues/new) if there are UI/UX changes
8. [ ] Create a testing plan for the reviewer and add it to the Testing section

## Description

Expand All @@ -16,6 +17,10 @@ Describe the outcome of your work here. A non-programmer who is familiar with Ko

Describe what you've changed and why. This should allow the reviewer to understand more easily the scope of the PR. It's good to be thorough here.

## Testing

Add a testing plan for the reviewer. This should allow the reviewer to test the basic functionality of the new code.

## Related issues

Fixes #ISSUE
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// Libraries
import React from 'react';

// Partial components
import Button from 'js/components/common/button';
import PaginatedQueryUniversalTable from 'js/universalTable/paginatedQueryUniversalTable.component';

// Utilities
import useAccessLogsQuery, {type AccessLog} from 'js/query/queries/accessLogs.query';
import {formatTime} from 'js/utils';
import sessionStore from 'js/stores/session';

// Styles
import securityStyles from 'js/account/security/securityRoute.module.scss';

export default function AccessLogsSection() {
function logOutAllSessions() {
sessionStore.logOutAll();
}

return (
<>
<header className={securityStyles.securityHeader}>
<h2 className={securityStyles.securityHeaderText}>
{t('Recent account activity')}
</h2>

{/* TODO: we comment this out until we know how to handle exsiting
sessions for the moment of release of the feature. */}
{/*<div className={securityStyles.securityHeaderActions}>
<Button
type='text'
size='m'
onClick={logOutAllSessions}
label={t('Log out of all devices')}
startIcon='logout'
/>
</div>*/}
</header>

<PaginatedQueryUniversalTable<AccessLog>
queryHook={useAccessLogsQuery}
columns={[
// The `key`s of these columns are matching the `AccessLog` interface
// properties (from `accessLogs.query.ts` file) using dot notation.
{key: 'metadata.source', label: t('Source')},
{
key: 'date_created',
label: t('Last activity'),
cellFormatter: (date: string) => formatTime(date),
},
{key: 'metadata.ip_address', label: t('IP Address')},
]}
/>
</>
);
}
53 changes: 31 additions & 22 deletions jsapp/js/account/security/apiToken/apiTokenSection.component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
// Libraries
import React, {
useState,
useEffect,
} from 'react';
import {dataInterface} from 'js/dataInterface';
import cx from 'classnames';

// Partial components
import TextBox from 'js/components/common/textBox';
import Button from 'js/components/common/button';

// Utils
import {dataInterface} from 'js/dataInterface';
import {notify} from 'js/utils';

// Styles
import styles from './apiTokenSection.module.scss';
import securityStyles from 'js/account/security/securityRoute.module.scss';

const HIDDEN_TOKEN_VALUE = '*'.repeat(40);

Expand Down Expand Up @@ -42,28 +51,28 @@ export default function ApiTokenDisplay() {
}, [isVisible]);

return (
<div className={styles.root}>
<div className={styles.titleSection}>
<h2 className={styles.title}>{t('API Key')}</h2>
</div>

<div className={styles.bodySection}>
<TextBox
type={isVisible && !isFetching && token !== null ? 'text' : 'password'}
value={token !== null ? token : HIDDEN_TOKEN_VALUE}
readOnly
/>
</div>
<section className={securityStyles.securitySection}>
<div className={securityStyles.securitySectionTitle}>
<h2 className={securityStyles.securitySectionTitleText}>{t('API Key')}</h2>
</div>

<div className={styles.optionsSection}>
<Button
label='Display'
size='m'
type='secondary'
onClick={toggleTokenVisibility}
/>
</div>
</div>
<div className={cx(securityStyles.securitySectionBody, styles.body)}>
<TextBox
type={isVisible && !isFetching && token !== null ? 'text' : 'password'}
value={token !== null ? token : HIDDEN_TOKEN_VALUE}
readOnly
className={styles.token}
/>
</div>

<div className={styles.options}>
<Button
label='Display'
size='m'
type='primary'
onClick={toggleTokenVisibility}
/>
</div>
</section>
);
}
60 changes: 10 additions & 50 deletions jsapp/js/account/security/apiToken/apiTokenSection.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,19 @@
@use 'scss/sizes';
@use 'scss/libs/_mdl';

.root {
margin-top: sizes.$x20;
margin-bottom: sizes.$x60;
padding-top: sizes.$x14;
display: flex;
align-items: baseline;
column-gap: sizes.$x16;
border-top: sizes.$x1 solid;
border-color: colors.$kobo-gray-300;
}

.titleSection {
flex: 2;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
}

.title {
margin: 0;
color: colors.$kobo-gray-700;
font-weight: 600;
line-height: 1.6;
.body {
flex: 5;
}

.bodySection {
flex: 6;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: wrap;
padding-left: sizes.$x4;

label {
display: inline-block;
vertical-align: top;
width: 100%;

input {
// API token display
font-family: mdl.$font_mono;
// 40 characters + padding + border
max-width: calc(40ch + 22px);
}
.token {
input {
// API token display
font-family: mdl.$font_mono;
}
}

.optionsSection {
flex: 2;
text-align: right;

button {
display: inline-block;
}
.options {
flex: 3;
display: flex;
justify-content: flex-end;
}
60 changes: 36 additions & 24 deletions jsapp/js/account/security/email/emailSection.component.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
// Libraries
import React, {useEffect, useState} from 'react';
import cx from 'classnames';

// Stores and email related
import sessionStore from 'js/stores/session';
import {
getUserEmails,
setUserEmail,
deleteUnverifiedUserEmails,
} from './emailSection.api';
import type {EmailResponse} from './emailSection.api';
import style from './emailSection.module.scss';

// Partial components
import Button from 'jsapp/js/components/common/button';
import TextBox from 'jsapp/js/components/common/textBox';
import Icon from 'jsapp/js/components/common/icon';
import {formatTime} from 'jsapp/js/utils';
import {notify} from 'js/utils';

// Utils
import {formatTime, notify} from 'js/utils';

// Styles
import styles from './emailSection.module.scss';
import securityStyles from 'js/account/security/securityRoute.module.scss';

interface EmailState {
emails: EmailResponse[];
Expand All @@ -23,9 +33,14 @@ interface EmailState {
export default function EmailSection() {
const [session] = useState(() => sessionStore);

let initialEmail = '';
if ('email' in session.currentAccount) {
initialEmail = session.currentAccount.email;
}

const [email, setEmail] = useState<EmailState>({
emails: [],
newEmail: '',
newEmail: initialEmail,
refreshedEmail: false,
refreshedEmailDate: '',
});
Expand All @@ -48,7 +63,7 @@ export default function EmailSection() {
newEmail: '',
});
});
});
}, () => {/* Avoid crashing app when 500 error happens */});
}

function deleteNewUserEmail() {
Expand Down Expand Up @@ -103,26 +118,31 @@ export default function EmailSection() {
);

return (
<div className={style.root}>
<div className={style.titleSection}>
<h2 className={style.title}>{t('Email address')}</h2>
<section className={securityStyles.securitySection}>
<div className={securityStyles.securitySectionTitle}>
<h2 className={securityStyles.securitySectionTitleText}>{t('Email address')}</h2>
</div>

<div className={style.bodySection}>
<div className={cx(securityStyles.securitySectionBody, styles.body)}>
{!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<p className={style.currentEmail}>{currentAccount.email}</p>
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>
)}

{unverifiedEmail?.email &&
!session.isPending &&
session.isInitialLoadComplete &&
'email' in currentAccount && (
<>
<div className={style.unverifiedEmail}>
<div className={styles.unverifiedEmail}>
<Icon name='alert' />
<p className={style['blurb']}>
<p className={styles.blurb}>
<strong>
{t('Check your email ##UNVERIFIED_EMAIL##. ').replace(
'##UNVERIFIED_EMAIL##',
Expand All @@ -136,7 +156,7 @@ export default function EmailSection() {
</p>
</div>

<div className={style.editEmail}>
<div className={styles.unverifiedEmailButtons}>
<Button
label='Resend'
size='m'
Expand Down Expand Up @@ -167,27 +187,19 @@ export default function EmailSection() {
</div>

<form
className={style.optionsSection}
className={styles.options}
onSubmit={(e) => {
e.preventDefault();
handleSubmit();
}}
>
{/*TODO: Move TextBox into a modal--it messes up the flow of the row right now*/}
<TextBox
value={email.newEmail}
placeholder={t('Type new email address')}
onChange={onTextFieldChange.bind(onTextFieldChange)}
type='email'
/>

<Button
label='Change'
size='m'
type='secondary'
type='primary'
onClick={handleSubmit}
/>
</form>
</div>
</section>
);
}
Loading

0 comments on commit b227f5b

Please sign in to comment.