Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added account pages from demo-store #1410

Merged
merged 2 commits into from
Jun 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12,145 changes: 12,145 additions & 0 deletions templates/demo-store-neue/package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion templates/demo-store-neue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
"dependencies": {
"@headlessui/react": "^1.6.3",
"@heroicons/react": "^1.0.6",
"@shopify/hydrogen": "^0.21.0",
"@shopify/hydrogen": "^0.22.1",
"clsx": "^1.1.1",
"graphql-tag": "^2.12.6",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"react-use": "^17.4.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {useMoney} from '@shopify/hydrogen';

/**
* A client component that defines the currency code, currency symbol, and amount of a product
*/
export default function MoneyPrice({money}) {
const {currencyCode, currencyNarrowSymbol, amount} = useMoney(money);
return (
<span className="text-black text-md">
{currencyCode}
{currencyNarrowSymbol}
{amount}
</span>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {useCallback} from 'react';

export default function LogoutButton(props) {
const logout = useCallback(() => {
fetch('/account/logout', {method: 'POST'}).then(
() => (window.location.href = '/'),
);
}, []);
return (
<button {...props} onClick={logout}>
Logout
</button>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import {
Seo,
useShopQuery,
NoStore,
flattenConnection,
gql,
} from '@shopify/hydrogen';

import Layout from '../layouts/DefaultLayout.server';
import LogoutButton from '../elements/LogoutButton.client';
import MoneyPrice from '../blocks/MoneyPrice.client';

export default function AccountDetails({customerAccessToken}) {
const {data} = useShopQuery({
query: QUERY,
variables: {
customerAccessToken,
},
cache: NoStore(),
});

const customer = data && data.customer;

const orders =
customer?.orders?.edges.length > 0
? flattenConnection(customer.orders)
: [];

const pageHeader = customer
? `Welcome ${customer.firstName || customer.email}!`
: 'Account Details';

return (
<Layout>
<Seo type="noindex" data={{title: 'Account details'}} />
<h1>{pageHeader}</h1>
<div className="flex items-center justify-between">
<LogoutButton className="inline-block align-baseline font-bold text-sm text-blue-500 hover:text-blue-800">
Logout
</LogoutButton>
</div>
<div className="bg-white shadow-md rounded px-8 pt-6 pb-8 mt-6 mb-4">
<h2>Order History</h2>
<table className="min-w-full table-fixed text-sm text-left mt-6">
<thead>
<tr>
<th>Order</th>
<th>Date</th>
<th>Payment Status</th>
<th>Fulfillment Status</th>
<th>Total</th>
</tr>
</thead>
<tbody>
{orders.map((order) => (
<tr key={order.id}>
<td>#{order.orderNumber}</td>
<td>{new Date(order.processedAt).toDateString()}</td>
<td>{order.financialStatus}</td>
<td>{order.fulfillmentStatus}</td>
<td>
<MoneyPrice money={order.currentTotalPrice} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</Layout>
);
}

const QUERY = gql`
query CustomerDetails($customerAccessToken: String!) {
customer(customerAccessToken: $customerAccessToken) {
firstName
email
orders(first: 250) {
edges {
node {
id
orderNumber
processedAt
financialStatus
fulfillmentStatus
currentTotalPrice {
amount
currencyCode
}
}
}
}
}
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import React from 'react';
import {useNavigate} from '@shopify/hydrogen/client';

export default function AccountActivateForm({id, activationToken}) {
const navigate = useNavigate();

const [submitError, setSubmitError] = React.useState(null);

const [password, setPassword] = React.useState('');
const [passwordError, setPasswordError] = React.useState(null);

const [passwordConfirm, setPasswordConfirm] = React.useState('');
const [passwordConfirmError, setPasswordConfirmError] = React.useState(null);

function passwordValidation(form) {
setPasswordError(null);
setPasswordConfirmError(null);

let hasError = false;

if (!form.password.validity.valid) {
hasError = true;
setPasswordError(
form.password.validity.valueMissing
? 'Please enter a password'
: 'Passwords must be at least 6 characters',
);
}

if (!form.passwordConfirm.validity.valid) {
hasError = true;
setPasswordConfirmError(
form.password.validity.valueMissing
? 'Please re-enter a password'
: 'Passwords must be at least 6 characters',
);
}

if (password !== passwordConfirm) {
hasError = true;
setPasswordConfirmError('The two password entered did not match.');
}

return hasError;
}

async function onSubmit(event) {
event.preventDefault();

if (passwordValidation(event.target)) {
return;
}

const response = await callActivateApi({
id,
activationToken,
password,
});

if (response.error) {
setSubmitError(response.error);
return;
}

navigate('/account');
}

return (
<div className="flex justify-center">
<div className="max-w-md w-full">
<h1 className="text-4xl">Activate Account.</h1>
<p className="mt-4">Create your password to activate your account.</p>
<form noValidate className="pt-6 pb-8 mt-4 mb-4" onSubmit={onSubmit}>
{submitError && (
<div className="flex items-center justify-center mb-6 bg-zinc-500">
<p className="m-4 text-s text-white">{submitError}</p>
</div>
)}
<div className="mb-4">
<input
className={`mb-1 appearance-none border w-full py-2 px-3 text-gray-800 placeholder:text-gray-500 leading-tight focus:shadow-outline ${
passwordError ? ' border-red-500' : 'border-gray-900'
}`}
id="password"
name="password"
type="password"
autoComplete="current-password"
placeholder="Password"
aria-label="Password"
value={password}
minLength={8}
required
onChange={(event) => {
setPassword(event.target.value);
}}
/>
<p
className={`text-red-500 text-xs ${
!passwordError ? 'invisible' : ''
}`}
>
{passwordError} &nbsp;
</p>
</div>
<div className="mb-4">
<input
className={`mb-1 appearance-none border w-full py-2 px-3 text-gray-800 placeholder:text-gray-500 leading-tight focus:shadow-outline ${
passwordConfirmError ? ' border-red-500' : 'border-gray-900'
}`}
id="passwordConfirm"
name="passwordConfirm"
type="password"
autoComplete="current-password"
placeholder="Re-enter password"
aria-label="Re-enter password"
value={passwordConfirm}
required
minLength={8}
onChange={(event) => {
setPasswordConfirm(event.target.value);
}}
/>
<p
className={`text-red-500 text-xs ${
!passwordConfirmError ? 'invisible' : ''
}`}
>
{passwordConfirmError} &nbsp;
</p>
</div>
<div className="flex items-center justify-between">
<button
className="bg-gray-900 text-white uppercase py-2 px-4 focus:shadow-outline block w-full"
type="submit"
>
Save
</button>
</div>
</form>
</div>
</div>
);
}

function callActivateApi({id, activationToken, password}) {
return fetch(`/account/activate`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({id, activationToken, password}),
})
.then((res) => {
if (res.ok) {
return {};
} else {
return res.json();
}
})
.catch((error) => {
return {
error: error.toString(),
};
});
}
Loading