Skip to content

Commit

Permalink
Merge pull request evershopcommerce#545 from evershopcommerce/dev
Browse files Browse the repository at this point in the history
Adding age gate extension source code for tutorial
  • Loading branch information
treoden committed May 22, 2024
2 parents 39af72a + 4199744 commit e86a670
Show file tree
Hide file tree
Showing 17 changed files with 333 additions and 0 deletions.
41 changes: 41 additions & 0 deletions packages/agegate/api/verifyAge/[bodyParser]verifyAge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const {
OK,
INTERNAL_SERVER_ERROR
} = require('@evershop/evershop/src/lib/util/httpStatus');
const { getSetting } = require('@evershop/evershop/src/modules/setting/services/setting');

// eslint-disable-next-line no-unused-vars
module.exports = async (request, response, delegate, next) => {
const { age } = request.body;
try {
response.status(OK);
const minAge = await getSetting('minAge');
if (age && age >= minAge) {
// Set the age verified cookie
response.cookie('age-verified', 1, {
maxAge: 1000 * 60 * 60 * 24 * 10
});
response.json({
data: {
age,
passed: true
}
});
} else {
response.json({
data: {
age,
passed: false
}
});
}
} catch (e) {
response.status(INTERNAL_SERVER_ERROR);
response.json({
error: {
status: INTERNAL_SERVER_ERROR,
message: e.message
}
});
}
};
5 changes: 5 additions & 0 deletions packages/agegate/api/verifyAge/[context]bodyParser[auth].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const bodyParser = require('body-parser');

module.exports = (request, response, delegate, next) => {
bodyParser.json({ inflate: false })(request, response, next);
};
11 changes: 11 additions & 0 deletions packages/agegate/api/verifyAge/payloadSchema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"type": "object",
"properties": {
"age": {
"type": ["integer", "string"],
"pattern": "^[0-9]+$"
}
},
"required": ["age"],
"additionalProperties": true
}
5 changes: 5 additions & 0 deletions packages/agegate/api/verifyAge/route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"methods": ["POST"],
"path": "/age-verify",
"access": "public"
}
3 changes: 3 additions & 0 deletions packages/agegate/graphql/types/AgeSetting/AgeSetting.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
extend type Setting {
minAge: Int!
}
12 changes: 12 additions & 0 deletions packages/agegate/graphql/types/AgeSetting/AgeSetting.resolvers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
Setting: {
minAge: (setting) => {
const minAge = setting.find((s) => s.name === 'minAge');
if (minAge) {
return minAge.value;
} else {
return 18;
}
}
}
};
79 changes: 79 additions & 0 deletions packages/agegate/pages/admin/ageSetting/AgeSetting.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import PropTypes from 'prop-types';
import React from 'react';
import { toast } from 'react-toastify';
import Area from '@components/common/Area';
import { Form } from '@components/common/form/Form';
import SettingMenu from '@components/admin/setting/SettingMenu';
import { Field } from '@components/common/form/Field';
import { Card } from '@components/admin/cms/Card';

export default function AgeSetting({ saveSettingApi, setting: { minAge } }) {
return (
<div className="main-content-inner">
<div className="grid grid-cols-6 gap-x-2 grid-flow-row ">
<div className="col-span-2">
<SettingMenu />
</div>
<div className="col-span-4">
<Form
id="ageSettingForm"
method="POST"
action={saveSettingApi}
onSuccess={(response) => {
if (!response.error) {
toast.success('Setting saved');
} else {
toast.error(response.error.message);
}
}}
>
<Card title="Age Setting">
<Card.Session>
<Area
id="ageSetting"
className="grid gap-2"
coreComponents={[
{
component: {
default: Field
},
props: {
name: 'minAge',
placeHolder: 'Minimum Age',
label: 'Minimum Age',
type: 'text',
value: minAge,
validationRules: ['notEmpty']
}
}
]}
/>
</Card.Session>
</Card>
</Form>
</div>
</div>
</div>
);
}

AgeSetting.propTypes = {
saveSettingApi: PropTypes.string.isRequired,
setting: PropTypes.shape({
minAge: PropTypes.number.isRequired
}).isRequired
};

export const layout = {
areaId: 'content',
sortOrder: 10
};

export const query = `
query Query {
saveSettingApi: url(routeId: "saveSetting"),
setting {
minAge
}
}
`;
8 changes: 8 additions & 0 deletions packages/agegate/pages/admin/ageSetting/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { setContextValue } = require("@evershop/evershop/src/modules/graphql/services/contextHelper");

module.exports = (request) => {
setContextValue(request, 'pageInfo', {
title: 'Age Setting',
description: 'Age Setting'
});
};
4 changes: 4 additions & 0 deletions packages/agegate/pages/admin/ageSetting/route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"methods": ["GET"],
"path": "/setting/agegate"
}
26 changes: 26 additions & 0 deletions packages/agegate/pages/admin/all/AgeSettingMenu.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Card } from '@components/admin/cms/Card';

export default function AgeSettingMenu({ ageSettingUrl }) {
return (
<Card.Session title={<a href={ageSettingUrl}>Age Setting</a>}>
<div>Configure the minimum age to access the site</div>
</Card.Session>
);
}

AgeSettingMenu.propTypes = {
ageSettingUrl: PropTypes.string.isRequired
};

export const layout = {
areaId: 'settingPageMenu',
sortOrder: 50
};

export const query = `
query Query {
ageSettingUrl: url(routeId: "ageSetting")
}
`;
74 changes: 74 additions & 0 deletions packages/agegate/pages/frontStore/ageGate/AgeForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Field } from '@components/common/form/Field';
import { Form } from '@components/common/form/Form';
import React from 'react';
import PropTypes from 'prop-types';
import { toast } from 'react-toastify';

function AgeForm({ action, homeUrl, failurePageUrl, setting: { minAge } }) {
return (
<div className="page-width p-2">
<Form
id="ageForm"
action={action}
method="POST"
onSuccess={(response) => {
if (!response.error) {
if (response.data.passed) {
window.location.href = homeUrl;
} else {
// Redirect to age verification failure page
window.location.href = failurePageUrl;
}
} else {
toast.error('Something wront. Please try again later');
}
}}
btnText="Submit"
>
<div className="text-center">
<h3>Age Verification</h3>
<p>
We only allow users who are {minAge} years and above. Please enter
your age to proceed.
</p>
</div>
<br />
<div className="form-group">
<Field
type="text"
name="age"
value={1}
validationRules={['notEmpty']}
/>
</div>
</Form>
</div>
);
}

AgeForm.propTypes = {
action: PropTypes.string.isRequired,
homeUrl: PropTypes.string.isRequired,
failurePageUrl: PropTypes.string.isRequired,
setting: PropTypes.shape({
minAge: PropTypes.number.isRequired
}).isRequired
};

export const layout = {
areaId: 'content',
sortOrder: 1
};

export const query = `
query Query {
action: url(routeId: "verifyAge"),
homeUrl: url(routeId: "homepage"),
failurePageUrl: url(routeId: "ageVerifyFailure"),
setting {
minAge
}
}
`;

export default AgeForm;
8 changes: 8 additions & 0 deletions packages/agegate/pages/frontStore/ageGate/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { setContextValue } = require("@evershop/evershop/src/modules/graphql/services/contextHelper");

module.exports = (request) => {
setContextValue(request, 'pageInfo', {
title: 'Age Gate',
description: 'Age Gate'
})
};
4 changes: 4 additions & 0 deletions packages/agegate/pages/frontStore/ageGate/route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"methods": ["GET"],
"path": "/age-gate"
}
21 changes: 21 additions & 0 deletions packages/agegate/pages/frontStore/ageVerifyFailure/Index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';

function Index() {
return (
<div className='page-width p-2'>
<div className='text-center p-2'>
<h3>Sorry!</h3>
<p>
You are not old enough to view this site. Sorry!!!
</p>
</div>
</div>
);
}

export const layout = {
areaId: 'content',
sortOrder: 1
}

export default Index;
8 changes: 8 additions & 0 deletions packages/agegate/pages/frontStore/ageVerifyFailure/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const { setContextValue } = require("@evershop/evershop/src/modules/graphql/services/contextHelper");

module.exports = (request) => {
setContextValue(request, 'pageInfo', {
title: 'Age verification failed',
description: 'Age verification failed'
});
};
4 changes: 4 additions & 0 deletions packages/agegate/pages/frontStore/ageVerifyFailure/route.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"methods": ["GET"],
"path": "/age-verify-failed"
}
20 changes: 20 additions & 0 deletions packages/agegate/pages/frontStore/all/checkAge.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const { buildUrl } = require('@evershop/evershop/src/lib/router/buildUrl');

module.exports = async (request, response, delegate, next) => {
// Get the age verify cookie
const ageVerifyCookie = request.cookies['age-verified'];
if (!ageVerifyCookie || parseInt(ageVerifyCookie, 10) !== 1) {
// Get the current route
const { currentRoute } = request;
if (
currentRoute.id === 'ageGate' ||
currentRoute.id === 'ageVerifyFailure'
) {
return next();
} else {
return response.redirect(buildUrl('ageGate'));
}
} else {
return next();
}
};

0 comments on commit e86a670

Please sign in to comment.